mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge remote-tracking branch 'remotes/origin/master' into notification
# Conflicts: # src/app/app.reducer.ts # src/app/shared/shared.module.ts
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
addons:
|
||||
- chrome: stable
|
||||
apt:
|
||||
sources:
|
||||
- google-chrome
|
||||
packages:
|
||||
- google-chrome-stable
|
||||
|
||||
language: node_js
|
||||
|
||||
@@ -21,6 +25,8 @@ install:
|
||||
- travis_retry yarn install
|
||||
|
||||
script:
|
||||
# Use Chromium instead of Chrome.
|
||||
- export CHROME_BIN=chromium-browser
|
||||
- yarn run build
|
||||
- yarn run ci
|
||||
- cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
||||
|
48
README.md
48
README.md
@@ -137,17 +137,32 @@ yarn run clean:dist
|
||||
Testing
|
||||
-------
|
||||
|
||||
### Unit Test
|
||||
### Test a Pull Request
|
||||
|
||||
Unit tests use Karma. You can find the configuration file at the same level of this README file:`./karma.conf.js` If you are going to use a remote test enviroment you need to edit the './karma.conf.js'. Follow the instructions you will find inside it. To executing tests whenever any file changes you can modify the 'autoWatch' option to 'true' and 'singleRun' option to 'false'. A coverage report is also available at: http://localhost:9876/ after you run:`yarn run coverage`.
|
||||
If you would like to contribute by testing a Pull Request (PR), here's how to do so. Keep in mind, you **do not need to have a DSpace backend / REST API installed locally to test a PR**. By default, the dspace-angular project points at our demo REST API
|
||||
|
||||
To correctly run the tests you need to run the build once with:`yarn run build`.
|
||||
1. Pull down the branch that the Pull Request was built from. Easy instructions for doing so can be found on the Pull Request itself.
|
||||
* Next to the "Merge" button, you'll see a link that says "command line instructions".
|
||||
* Click it, and follow "Step 1" of those instructions to checkout the pull down the PR branch.
|
||||
2. `yarn run clean` (This resets your local dependencies to ensure you are up-to-date with this PR)
|
||||
3. `yarn install` (Updates your local dependencies to those in the PR)
|
||||
4. `yarn start` (Rebuilds the project, and deploys to localhost:3000, by default)
|
||||
5. At this point, the code from the PR will be deployed to http://localhost:3000. Test it out, and ensure that it does what is described in the PR (or fixes the bug described in the ticket linked to the PR).
|
||||
|
||||
Once you have tested the Pull Request, please add a comment and/or approval to the PR to let us know whether you found it to be successful (or not). Thanks!
|
||||
|
||||
|
||||
### Unit Tests
|
||||
|
||||
Unit tests use Karma. You can find the configuration file at the same level of this README file:`./karma.conf.js` If you are going to use a remote test enviroment you need to edit the `./karma.conf.js`. Follow the instructions you will find inside it. To executing tests whenever any file changes you can modify the 'autoWatch' option to 'true' and 'singleRun' option to 'false'. A coverage report is also available at: http://localhost:9876/ after you run: `yarn run coverage`.
|
||||
|
||||
To correctly run the tests you need to run the build once with: `yarn run build`.
|
||||
|
||||
The default browser is Google Chrome.
|
||||
|
||||
Place your tests in the same location of the application source code files that they test.
|
||||
|
||||
and run:`yarn run test`
|
||||
and run: `yarn run test`
|
||||
|
||||
### E2E test
|
||||
|
||||
@@ -161,15 +176,18 @@ Protractor needs a functional instance of the DSpace interface to run the E2E te
|
||||
|
||||
or any command that bring up the DSpace interface.
|
||||
|
||||
Place your tests at the following path:`./e2e`
|
||||
Place your tests at the following path: `./e2e`
|
||||
|
||||
and run:`yarn run e2e`
|
||||
and run: `yarn run e2e`
|
||||
|
||||
### Continuous Integration (CI) Test
|
||||
|
||||
To run all the tests (e.g.: to run tests with Continuous Integration software) you can execute:`yarn run ci` Keep in mind that this command prerequisites are the sum of unit test and E2E tests.
|
||||
|
||||
##Documentation To build the code documentation we use [TYPEDOC](http://typedoc.org). TYPEDOC is a documentation generator for TypeScript projects. It extracts informations from properly formatted comments that can be written within the code files. Follow the instructions [here](http://typedoc.org/guides/doccomments/) to know how to make those comments.
|
||||
Documentation
|
||||
--------------
|
||||
|
||||
To build the code documentation we use [TYPEDOC](http://typedoc.org). TYPEDOC is a documentation generator for TypeScript projects. It extracts informations from properly formatted comments that can be written within the code files. Follow the instructions [here](http://typedoc.org/guides/doccomments/) to know how to make those comments.
|
||||
|
||||
Run:`yarn run docs` to produce the documentation that will be available in the 'doc' folder.
|
||||
|
||||
@@ -322,8 +340,8 @@ Install your library via `yarn add lib-name --save` and import it in your code.
|
||||
If the library does not include typings, you can install them using yarn:
|
||||
|
||||
```bash
|
||||
yarn add d3 --save
|
||||
yarn add @types/d3 --save-dev
|
||||
yarn add d3
|
||||
yarn add @types/d3 --dev
|
||||
```
|
||||
|
||||
If the library doesn't have typings available at `@types/`, you can still use it by manually adding typings for it:
|
||||
@@ -349,14 +367,18 @@ If you're importing a module that uses CommonJS you need to import as
|
||||
import * as _ from 'lodash';
|
||||
```
|
||||
|
||||
yarn lockfile
|
||||
Managing Dependencies (via yarn)
|
||||
-------------
|
||||
|
||||
This project makes use of yarn to ensure that the exact same dependency versions are used every time you install it.
|
||||
This project makes use of [`yarn`](https://yarnpkg.com/en/) to ensure that the exact same dependency versions are used every time you install it.
|
||||
|
||||
yarn creates the file [`yarn.lock`](https://yarnpkg.com/en/docs/yarn-lock) to track those versions. That file is updated automatically every time you install a new dependency from the commandline (by using `yarn add some-lib --save` or `yarn add some-lib --save-dev`).
|
||||
* `yarn` creates a [`yarn.lock`](https://yarnpkg.com/en/docs/yarn-lock) to track those versions. That file is updated automatically by whenever dependencies are added/updated/removed via yarn.
|
||||
* **Adding new dependencies**: To install/add a new dependency (third party library), use [`yarn add`](https://yarnpkg.com/en/docs/cli/add). For example: `yarn add some-lib`.
|
||||
* If you are adding a new build tool dependency (to `devDependencies`), use `yarn add some-lib --dev`
|
||||
* **Upgrading existing dependencies**: To upgrade existing dependencies, you can use [`yarn upgrade`](https://yarnpkg.com/en/docs/cli/upgrade). For example: `yarn upgrade some-lib` or `yarn upgrade some-lib@version`
|
||||
* **Removing dependencies**: If a dependency is no longer needed, or replaced, use [`yarn remove`](https://yarnpkg.com/en/docs/cli/remove) to remove it.
|
||||
|
||||
If you manually add a package or change a version in `package.json` you'll have to update yarn's lock file as well. You can do so by running `yarn upgrade`
|
||||
As you can see above, using `yarn` commandline tools means that you should never need to modify the `package.json` manually. *We recommend always using `yarn` to keep dependencies updated / in sync.*
|
||||
|
||||
Frequently asked questions
|
||||
--------------------------
|
||||
|
90
package.json
90
package.json
@@ -69,26 +69,26 @@
|
||||
"coverage": "http-server -c-1 -o -p 9875 ./coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "5.2.1",
|
||||
"@angular/common": "5.2.1",
|
||||
"@angular/core": "5.2.1",
|
||||
"@angular/forms": "5.2.1",
|
||||
"@angular/http": "5.2.1",
|
||||
"@angular/platform-browser": "5.2.1",
|
||||
"@angular/platform-browser-dynamic": "5.2.1",
|
||||
"@angular/platform-server": "5.2.1",
|
||||
"@angular/router": "5.2.1",
|
||||
"@angular/animations": "^5.2.5",
|
||||
"@angular/common": "^5.2.5",
|
||||
"@angular/core": "^5.2.5",
|
||||
"@angular/forms": "^5.2.5",
|
||||
"@angular/http": "^5.2.5",
|
||||
"@angular/platform-browser": "^5.2.5",
|
||||
"@angular/platform-browser-dynamic": "^5.2.5",
|
||||
"@angular/platform-server": "^5.2.5",
|
||||
"@angular/router": "^5.2.5",
|
||||
"@angularclass/bootloader": "1.0.1",
|
||||
"@ng-bootstrap/ng-bootstrap": "1.0.0-beta.9",
|
||||
"@ngrx/effects": "4.1.1",
|
||||
"@ngrx/router-store": "4.1.1",
|
||||
"@ngrx/store": "4.1.1",
|
||||
"@ng-bootstrap/ng-bootstrap": "^1.0.0",
|
||||
"@ngrx/effects": "^5.1.0",
|
||||
"@ngrx/router-store": "^5.0.1",
|
||||
"@ngrx/store": "^5.1.0",
|
||||
"@nguniversal/express-engine": "5.0.0-beta.5",
|
||||
"@ngx-translate/core": "9.1.1",
|
||||
"@ngx-translate/http-loader": "2.0.1",
|
||||
"angular-idle-preload": "2.0.4",
|
||||
"body-parser": "1.18.2",
|
||||
"bootstrap": "4.0.0-beta",
|
||||
"bootstrap": "^4.0.0",
|
||||
"cerialize": "0.1.18",
|
||||
"compression": "1.7.1",
|
||||
"cookie-parser": "1.4.3",
|
||||
@@ -106,45 +106,45 @@
|
||||
"pem": "1.12.3",
|
||||
"reflect-metadata": "0.1.12",
|
||||
"rxjs": "5.5.6",
|
||||
"ts-md5": "1.2.3",
|
||||
"ts-md5": "^1.2.4",
|
||||
"uuid": "^3.2.1",
|
||||
"webfontloader": "1.6.28",
|
||||
"zone.js": "0.8.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/compiler": "^5.2.1",
|
||||
"@angular/compiler-cli": "^5.2.1",
|
||||
"@ngrx/store-devtools": "4.1.1",
|
||||
"@ngtools/webpack": "1.9.5",
|
||||
"@angular/compiler": "^5.2.5",
|
||||
"@angular/compiler-cli": "^5.2.5",
|
||||
"@ngrx/store-devtools": "^5.1.0",
|
||||
"@ngtools/webpack": "^1.10.0",
|
||||
"@types/cookie-parser": "1.4.1",
|
||||
"@types/deep-freeze": "0.1.1",
|
||||
"@types/express": "4.11.0",
|
||||
"@types/express": "^4.11.1",
|
||||
"@types/express-serve-static-core": "4.11.1",
|
||||
"@types/hammerjs": "2.0.35",
|
||||
"@types/jasmine": "2.8.4",
|
||||
"@types/jasmine": "^2.8.6",
|
||||
"@types/memory-cache": "0.2.0",
|
||||
"@types/mime": "2.0.0",
|
||||
"@types/node": "^9.3.0",
|
||||
"@types/node": "^9.4.6",
|
||||
"@types/serve-static": "1.13.1",
|
||||
"@types/uuid": "^3.4.3",
|
||||
"@types/webfontloader": "1.6.29",
|
||||
"ajv": "6.0.1",
|
||||
"ajv-keywords": "3.0.0",
|
||||
"ajv": "^6.1.1",
|
||||
"ajv-keywords": "^3.1.0",
|
||||
"angular2-template-loader": "0.6.2",
|
||||
"autoprefixer": "7.2.5",
|
||||
"autoprefixer": "^8.0.0",
|
||||
"awesome-typescript-loader": "3.4.1",
|
||||
"caniuse-lite": "1.0.30000792",
|
||||
"caniuse-lite": "^1.0.30000697",
|
||||
"codelyzer": "^4.1.0",
|
||||
"compression-webpack-plugin": "1.1.3",
|
||||
"copy-webpack-plugin": "4.3.1",
|
||||
"compression-webpack-plugin": "^1.1.6",
|
||||
"copy-webpack-plugin": "^4.4.1",
|
||||
"coveralls": "3.0.0",
|
||||
"css-loader": "0.28.9",
|
||||
"deep-freeze": "0.0.1",
|
||||
"exports-loader": "0.6.4",
|
||||
"exports-loader": "^0.7.0",
|
||||
"html-webpack-plugin": "2.30.1",
|
||||
"imports-loader": "0.7.1",
|
||||
"istanbul-instrumenter-loader": "3.0.0",
|
||||
"jasmine-core": "2.9.1",
|
||||
"jasmine-core": "^2.99.1",
|
||||
"jasmine-marbles": "0.2.0",
|
||||
"jasmine-spec-reporter": "4.2.1",
|
||||
"json-loader": "0.5.7",
|
||||
@@ -156,31 +156,31 @@
|
||||
"karma-jasmine": "1.1.1",
|
||||
"karma-mocha-reporter": "2.2.5",
|
||||
"karma-phantomjs-launcher": "1.0.4",
|
||||
"karma-remap-coverage": "0.1.4",
|
||||
"karma-remap-coverage": "^0.1.5",
|
||||
"karma-remap-istanbul": "0.6.0",
|
||||
"karma-sourcemap-loader": "0.3.7",
|
||||
"karma-webdriver-launcher": "1.0.5",
|
||||
"karma-webpack": "2.0.9",
|
||||
"ngrx-store-freeze": "0.2.0",
|
||||
"node-sass": "4.7.2",
|
||||
"nodemon": "1.14.11",
|
||||
"ngrx-store-freeze": "^0.2.1",
|
||||
"node-sass": "^4.7.2",
|
||||
"nodemon": "^1.15.0",
|
||||
"npm-run-all": "4.1.2",
|
||||
"postcss": "6.0.16",
|
||||
"postcss": "^6.0.18",
|
||||
"postcss-apply": "0.8.0",
|
||||
"postcss-cli": "4.1.1",
|
||||
"postcss-cli": "^5.0.0",
|
||||
"postcss-cssnext": "3.1.0",
|
||||
"postcss-loader": "2.0.10",
|
||||
"postcss-loader": "^2.1.0",
|
||||
"postcss-responsive-type": "1.0.0",
|
||||
"postcss-smart-import": "0.7.6",
|
||||
"protractor": "5.2.2",
|
||||
"protractor": "^5.3.0",
|
||||
"protractor-istanbul-plugin": "2.0.0",
|
||||
"raw-loader": "0.5.1",
|
||||
"resolve-url-loader": "2.2.1",
|
||||
"rimraf": "2.6.2",
|
||||
"rollup": "0.54.1",
|
||||
"rollup-plugin-commonjs": "8.2.6",
|
||||
"rollup": "^0.56.0",
|
||||
"rollup-plugin-commonjs": "^8.3.0",
|
||||
"rollup-plugin-node-globals": "1.1.0",
|
||||
"rollup-plugin-node-resolve": "3.0.2",
|
||||
"rollup-plugin-node-resolve": "^3.0.3",
|
||||
"rollup-plugin-uglify": "3.0.0",
|
||||
"sass-loader": "6.0.6",
|
||||
"script-ext-html-webpack-plugin": "1.8.8",
|
||||
@@ -191,11 +191,11 @@
|
||||
"ts-helpers": "1.1.2",
|
||||
"ts-node": "4.1.0",
|
||||
"tslint": "5.9.1",
|
||||
"typedoc": "0.9.0",
|
||||
"typedoc": "^0.9.0",
|
||||
"typescript": "2.6.2",
|
||||
"webpack": "^3.10.0",
|
||||
"webpack-bundle-analyzer": "2.9.2",
|
||||
"webpack-dev-middleware": "2.0.4",
|
||||
"webpack": "^3.11.0",
|
||||
"webpack-bundle-analyzer": "^2.10.0",
|
||||
"webpack-dev-middleware": "^2.0.5",
|
||||
"webpack-dev-server": "2.11.1",
|
||||
"webpack-merge": "4.1.1",
|
||||
"webpack-node-externals": "1.6.0"
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<div *ngIf="searchResults?.hasSucceeded" @fadeIn>
|
||||
<div *ngIf="searchResults?.hasSucceeded && !searchResults?.isLoading" @fadeIn>
|
||||
<h2 *ngIf="searchResults?.payload ?.length > 0">{{ 'search.results.head' | translate }}</h2>
|
||||
<ds-viewable-collection
|
||||
[config]="searchConfig.pagination"
|
||||
|
@@ -23,6 +23,7 @@ body {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
|
@@ -12,6 +12,7 @@ import {
|
||||
SearchFiltersState
|
||||
} from './+search-page/search-filters/search-filter/search-filter.reducer';
|
||||
import { notificationsReducer, NotificationsState } from './shared/notifications/notifications.reducers';
|
||||
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
|
||||
|
||||
export interface AppState {
|
||||
router: fromRouter.RouterReducerState;
|
||||
@@ -20,6 +21,7 @@ export interface AppState {
|
||||
notifications: NotificationsState;
|
||||
searchSidebar: SearchSidebarState;
|
||||
searchFilter: SearchFiltersState;
|
||||
truncatable: TruncatablesState;
|
||||
}
|
||||
|
||||
export const appReducers: ActionReducerMap<AppState> = {
|
||||
@@ -28,5 +30,6 @@ export const appReducers: ActionReducerMap<AppState> = {
|
||||
header: headerReducer,
|
||||
notifications: notificationsReducer,
|
||||
searchSidebar: sidebarReducer,
|
||||
searchFilter: filterReducer
|
||||
searchFilter: filterReducer,
|
||||
truncatable: truncatableReducer
|
||||
};
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
|
||||
<nav class="navbar navbar-dark bg-primary navbar-expand-md">
|
||||
<div [ngClass]="{'clearfix': !(isNavBarCollapsed | async)}">
|
||||
<a class="navbar-brand" routerLink="/home">{{ 'title' | translate }}</a>
|
||||
</div>
|
||||
|
19
src/app/shared/animations/focus.ts
Normal file
19
src/app/shared/animations/focus.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { animate, state, transition, trigger, style } from '@angular/animations';
|
||||
|
||||
export const focusShadow = trigger('focusShadow', [
|
||||
|
||||
state('focus', style({ 'box-shadow': 'rgba(119, 119, 119, 0.6) 0px 0px 6px' })),
|
||||
|
||||
state('blur', style({ 'box-shadow': 'none' })),
|
||||
|
||||
transition('focus <=> blur', animate(250))
|
||||
]);
|
||||
|
||||
export const focusBackground = trigger('focusBackground', [
|
||||
|
||||
state('focus', style({ 'background-color': 'rgba(119, 119, 119, 0.1)' })),
|
||||
|
||||
state('blur', style({ 'background-color': 'transparent' })),
|
||||
|
||||
transition('focus <=> blur', animate(250))
|
||||
]);
|
10
src/app/shared/animations/overlay.ts
Normal file
10
src/app/shared/animations/overlay.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { animate, state, transition, trigger, style } from '@angular/animations';
|
||||
|
||||
export const overlay = trigger('overlay', [
|
||||
|
||||
state('show', style({ opacity: 0.5 })),
|
||||
|
||||
state('hide', style({ opacity: 0 })),
|
||||
|
||||
transition('show <=> hide', animate(250))
|
||||
]);
|
@@ -0,0 +1,5 @@
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { SearchResult } from '../../../+search-page/search-result.model';
|
||||
|
||||
export class CollectionSearchResult extends SearchResult<Collection> {
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
import { SearchResult } from '../../../+search-page/search-result.model';
|
||||
import { Community } from '../../../core/shared/community.model';
|
||||
|
||||
export class CommunitySearchResult extends SearchResult<Community> {
|
||||
}
|
@@ -1,21 +1,13 @@
|
||||
import { CollectionGridElementComponent } from './collection-grid-element.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { RouterStub } from '../../testing/router-stub';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
|
||||
let collectionGridElementComponent: CollectionGridElementComponent;
|
||||
let fixture: ComponentFixture<CollectionGridElementComponent>;
|
||||
const queryParam = 'test query';
|
||||
const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f';
|
||||
const activatedRouteStub = {
|
||||
queryParams: Observable.of({
|
||||
query: queryParam,
|
||||
scope: scopeParam
|
||||
})
|
||||
};
|
||||
const mockCollection: Collection = Object.assign(new Collection(), {
|
||||
|
||||
const mockCollectionWithAbstract: Collection = Object.assign(new Collection(), {
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.description.abstract',
|
||||
@@ -23,37 +15,56 @@ const mockCollection: Collection = Object.assign(new Collection(), {
|
||||
value: 'Short description'
|
||||
}]
|
||||
});
|
||||
const createdGridElementComponent:CollectionGridElementComponent= new CollectionGridElementComponent(mockCollection);
|
||||
|
||||
const mockCollectionWithoutAbstract: Collection = Object.assign(new Collection(), {
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
language: 'en_US',
|
||||
value: 'Test title'
|
||||
}]
|
||||
});
|
||||
|
||||
describe('CollectionGridElementComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CollectionGridElementComponent ],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||
{ provide: Router, useClass: RouterStub },
|
||||
{ provide: 'objectElementProvider', useValue: (createdGridElementComponent)}
|
||||
{ provide: 'objectElementProvider', useValue: (mockCollectionWithAbstract)}
|
||||
],
|
||||
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
}).compileComponents(); // compile template and css
|
||||
}).overrideComponent(CollectionGridElementComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(CollectionGridElementComponent);
|
||||
collectionGridElementComponent = fixture.componentInstance;
|
||||
}));
|
||||
|
||||
it('should show the collection cards in the grid element',() => {
|
||||
expect(fixture.debugElement.query(By.css('ds-collection-grid-element'))).toBeDefined();
|
||||
describe('When the collection has an abstract', () => {
|
||||
beforeEach(() => {
|
||||
collectionGridElementComponent.object = mockCollectionWithAbstract;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the description paragraph', () => {
|
||||
const collectionAbstractField = fixture.debugElement.query(By.css('p.card-text'));
|
||||
expect(collectionAbstractField).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('should only show the description if "short description" metadata is present',() => {
|
||||
const descriptionText = expect(fixture.debugElement.query(By.css('p.card-text')));
|
||||
describe('When the collection has no abstract', () => {
|
||||
beforeEach(() => {
|
||||
collectionGridElementComponent.object = mockCollectionWithoutAbstract;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
if (mockCollection.shortDescription.length > 0) {
|
||||
expect(descriptionText).toBeDefined();
|
||||
} else {
|
||||
expect(descriptionText).not.toBeDefined();
|
||||
}
|
||||
it('should not show the description paragraph', () => {
|
||||
const collectionAbstractField = fixture.debugElement.query(By.css('p.card-text'));
|
||||
expect(collectionAbstractField).toBeNull();
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
|
@@ -1,25 +1,13 @@
|
||||
import { CommunityGridElementComponent } from './community-grid-element.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { RouterStub } from '../../testing/router-stub';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { ListableObject } from '../../object-collection/shared/listable-object.model';
|
||||
import { Community } from '../../../core/shared/community.model';
|
||||
|
||||
let communityGridElementComponent: CommunityGridElementComponent;
|
||||
let fixture: ComponentFixture<CommunityGridElementComponent>;
|
||||
const queryParam = 'test query';
|
||||
const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f';
|
||||
const activatedRouteStub = {
|
||||
queryParams: Observable.of({
|
||||
query: queryParam,
|
||||
scope: scopeParam
|
||||
})
|
||||
};
|
||||
|
||||
const mockCommunity: Community = Object.assign(new Community(), {
|
||||
const mockCommunityWithAbstract: Community = Object.assign(new Community(), {
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.description.abstract',
|
||||
@@ -28,39 +16,55 @@ const mockCommunity: Community = Object.assign(new Community(), {
|
||||
}]
|
||||
});
|
||||
|
||||
const createdGridElementComponent:CommunityGridElementComponent= new CommunityGridElementComponent(mockCommunity);
|
||||
const mockCommunityWithoutAbstract: Community = Object.assign(new Community(), {
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
language: 'en_US',
|
||||
value: 'Test title'
|
||||
}]
|
||||
});
|
||||
|
||||
describe('CommunityGridElementComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CommunityGridElementComponent ],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||
{ provide: Router, useClass: RouterStub },
|
||||
{ provide: 'objectElementProvider', useValue: (createdGridElementComponent)}
|
||||
{ provide: 'objectElementProvider', useValue: (mockCommunityWithAbstract)}
|
||||
],
|
||||
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
}).compileComponents(); // compile template and css
|
||||
}).overrideComponent(CommunityGridElementComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(CommunityGridElementComponent);
|
||||
communityGridElementComponent = fixture.componentInstance;
|
||||
|
||||
}));
|
||||
|
||||
it('should show the community cards in the grid element',() => {
|
||||
expect(fixture.debugElement.query(By.css('ds-community-grid-element'))).toBeDefined();
|
||||
})
|
||||
describe('When the community has an abstract', () => {
|
||||
beforeEach(() => {
|
||||
communityGridElementComponent.object = mockCommunityWithAbstract;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should only show the description if "short description" metadata is present',() => {
|
||||
const descriptionText = expect(fixture.debugElement.query(By.css('p.card-text')));
|
||||
it('should show the description paragraph', () => {
|
||||
const communityAbstractField = fixture.debugElement.query(By.css('p.card-text'));
|
||||
expect(communityAbstractField).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
if (mockCommunity.shortDescription.length > 0) {
|
||||
expect(descriptionText).toBeDefined();
|
||||
} else {
|
||||
expect(descriptionText).not.toBeDefined();
|
||||
}
|
||||
describe('When the community has no abstract', () => {
|
||||
beforeEach(() => {
|
||||
communityGridElementComponent.object = mockCommunityWithoutAbstract;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should not show the description paragraph', () => {
|
||||
const communityAbstractField = fixture.debugElement.query(By.css('p.card-text'));
|
||||
expect(communityAbstractField).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -6,12 +6,11 @@
|
||||
</a>
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">{{object.findMetadata('dc.title')}}</h4>
|
||||
|
||||
<p *ngIf="object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);" class="item-authors card-text text-muted">
|
||||
<p *ngIf="object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0" class="item-authors card-text text-muted">
|
||||
<span *ngFor="let authorMd of object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">{{authorMd.value}}
|
||||
<span *ngIf="!last">; </span>
|
||||
</span>
|
||||
<span *ngIf="object.findMetadata('dc.date.issued')">{{object.findMetadata("dc.date.issued")}}</span>
|
||||
<span *ngIf="object.findMetadata('dc.date.issued')" class="item-date">{{object.findMetadata("dc.date.issued")}}</span>
|
||||
</p>
|
||||
|
||||
<p *ngIf="object.findMetadata('dc.description.abstract')" class="item-abstract card-text">{{object.findMetadata("dc.description.abstract") | dsTruncate:[200] }}</p>
|
||||
|
@@ -1,47 +1,55 @@
|
||||
import { ItemGridElementComponent } from './item-grid-element.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { RouterStub } from '../../testing/router-stub';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TruncatePipe } from '../../utils/truncate.pipe';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
let itemGridElementComponent: ItemGridElementComponent;
|
||||
let fixture: ComponentFixture<ItemGridElementComponent>;
|
||||
const queryParam = 'test query';
|
||||
const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f';
|
||||
const activatedRouteStub = {
|
||||
queryParams: Observable.of({
|
||||
query: queryParam,
|
||||
scope: scopeParam
|
||||
})
|
||||
};
|
||||
/* tslint:disable:no-shadowed-variable */
|
||||
const mockItem: Item = Object.assign(new Item(), {
|
||||
|
||||
const mockItemWithAuthorAndDate: Item = Object.assign(new Item(), {
|
||||
bitstreams: Observable.of({}),
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.contributor.author',
|
||||
language: 'en_US',
|
||||
value: 'Smith, Donald'
|
||||
},
|
||||
{
|
||||
key: 'dc.date.issued',
|
||||
language: null,
|
||||
value: '2015-06-26'
|
||||
}]
|
||||
});
|
||||
const mockItemWithoutAuthorAndDate: Item = Object.assign(new Item(), {
|
||||
bitstreams: Observable.of({}),
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
language: 'en_US',
|
||||
value: 'This is just another title'
|
||||
},
|
||||
{
|
||||
key: 'dc.type',
|
||||
language: null,
|
||||
value: 'Article'
|
||||
}]
|
||||
});
|
||||
|
||||
const createdGridElementComponent:ItemGridElementComponent= new ItemGridElementComponent(mockItem);
|
||||
|
||||
describe('ItemGridElementComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ItemGridElementComponent , TruncatePipe],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||
{ provide: Router, useClass: RouterStub },
|
||||
{ provide: 'objectElementProvider', useValue: {createdGridElementComponent}}
|
||||
{ provide: 'objectElementProvider', useValue: {mockItemWithAuthorAndDate}}
|
||||
],
|
||||
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
}).compileComponents(); // compile template and css
|
||||
}).overrideComponent(ItemGridElementComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
@@ -50,18 +58,51 @@ describe('ItemGridElementComponent', () => {
|
||||
|
||||
}));
|
||||
|
||||
it('should show the item cards in the grid element',() => {
|
||||
expect(fixture.debugElement.query(By.css('ds-item-grid-element'))).toBeDefined()
|
||||
describe('When the item has an author', () => {
|
||||
beforeEach(() => {
|
||||
itemGridElementComponent.object = mockItemWithAuthorAndDate;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the author paragraph', () => {
|
||||
const itemAuthorField = fixture.debugElement.query(By.css('p.item-authors'));
|
||||
expect(itemAuthorField).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('should only show the author span if the author metadata is present',() => {
|
||||
const itemAuthorField = expect(fixture.debugElement.query(By.css('p.item-authors')));
|
||||
describe('When the item has no author', () => {
|
||||
beforeEach(() => {
|
||||
itemGridElementComponent.object = mockItemWithoutAuthorAndDate;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
if (mockItem.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0) {
|
||||
expect(itemAuthorField).toBeDefined();
|
||||
} else {
|
||||
expect(itemAuthorField).toBeDefined();
|
||||
}
|
||||
it('should not show the author paragraph', () => {
|
||||
const itemAuthorField = fixture.debugElement.query(By.css('p.item-authors'));
|
||||
expect(itemAuthorField).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
})
|
||||
describe('When the item has an issuedate', () => {
|
||||
beforeEach(() => {
|
||||
itemGridElementComponent.object = mockItemWithAuthorAndDate;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the issuedate span', () => {
|
||||
const itemAuthorField = fixture.debugElement.query(By.css('span.item-date'));
|
||||
expect(itemAuthorField).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the item has no issuedate', () => {
|
||||
beforeEach(() => {
|
||||
itemGridElementComponent.object = mockItemWithoutAuthorAndDate;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should not show the issuedate span', () => {
|
||||
const dateField = fixture.debugElement.query(By.css('span.item-date'));
|
||||
expect(dateField).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -10,8 +10,8 @@
|
||||
(sortDirectionChange)="onSortDirectionChange($event)"
|
||||
(sortFieldChange)="onSortFieldChange($event)"
|
||||
(paginationChange)="onPaginationChange($event)">
|
||||
<div class="row mt-2" *ngIf="objects?.hasSucceeded" @fadeIn>
|
||||
<div class="col-lg-4 col-sm-6 col-xs-12 "
|
||||
<div class="card-columns" *ngIf="objects?.hasSucceeded" @fadeIn>
|
||||
<div
|
||||
*ngFor="let object of objects?.payload?.page">
|
||||
<ds-wrapper-grid-element [object]="object"></ds-wrapper-grid-element>
|
||||
</div>
|
||||
|
@@ -1,27 +1,24 @@
|
||||
@import '../../../styles/variables';
|
||||
@import '../../../styles/mixins';
|
||||
|
||||
ds-wrapper-grid-element ::ng-deep {
|
||||
div.thumbnail > img {
|
||||
height: $card-thumbnail-height;
|
||||
width: 100%;
|
||||
}
|
||||
.card-title {
|
||||
line-height: $headings-line-height;
|
||||
height: ($headings-line-height*3) +em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.item-abstract {
|
||||
line-height: $line-height-base;
|
||||
height: ($line-height-base*5)+em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.item-authors{
|
||||
line-height: $line-height-base;
|
||||
height: ($line-height-base*1.5)+em;
|
||||
}
|
||||
div.card {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: $spacer;
|
||||
}
|
||||
}
|
||||
|
||||
.card-columns {
|
||||
@include media-breakpoint-only(lg) {
|
||||
column-count: 3;
|
||||
}
|
||||
@include media-breakpoint-only(sm) {
|
||||
column-count: 2;
|
||||
}
|
||||
@include media-breakpoint-only(xs) {
|
||||
column-count: 1;
|
||||
}
|
||||
}
|
@@ -1,64 +1,83 @@
|
||||
import {CollectionSearchResultGridElementComponent } from './collection-search-result-grid-element.component';
|
||||
import { CollectionSearchResultGridElementComponent } from './collection-search-result-grid-element.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { RouterStub } from '../../../testing/router-stub';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TruncatePipe } from '../../../utils/truncate.pipe';
|
||||
import { Community } from '../../../../core/shared/community.model';
|
||||
import { Collection } from '../../../../core/shared/collection.model';
|
||||
import { TruncatableService } from '../../../truncatable/truncatable.service';
|
||||
import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
|
||||
|
||||
let collectionSearchResultGridElementComponent: CollectionSearchResultGridElementComponent;
|
||||
let fixture: ComponentFixture<CollectionSearchResultGridElementComponent>;
|
||||
const queryParam = 'test query';
|
||||
const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f';
|
||||
const activatedRouteStub = {
|
||||
queryParams: Observable.of({
|
||||
query: queryParam,
|
||||
scope: scopeParam
|
||||
})
|
||||
|
||||
const truncatableServiceStub: any = {
|
||||
isCollapsed: (id: number) => Observable.of(true),
|
||||
};
|
||||
const mockCollection: Collection = Object.assign(new Collection(), {
|
||||
|
||||
const mockCollectionWithAbstract: CollectionSearchResult = new CollectionSearchResult();
|
||||
mockCollectionWithAbstract.hitHighlights = [];
|
||||
mockCollectionWithAbstract.dspaceObject = Object.assign(new Collection(), {
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.description.abstract',
|
||||
language: 'en_US',
|
||||
value: 'Short description'
|
||||
} ]
|
||||
|
||||
});
|
||||
|
||||
const createdGridElementComponent: CollectionSearchResultGridElementComponent = new CollectionSearchResultGridElementComponent(mockCollection);
|
||||
const mockCollectionWithoutAbstract: CollectionSearchResult = new CollectionSearchResult();
|
||||
mockCollectionWithoutAbstract.hitHighlights = [];
|
||||
mockCollectionWithoutAbstract.dspaceObject = Object.assign(new Collection(), {
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
language: 'en_US',
|
||||
value: 'Test title'
|
||||
} ]
|
||||
});
|
||||
|
||||
describe('CollectionSearchResultGridElementComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CollectionSearchResultGridElementComponent, TruncatePipe ],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||
{ provide: Router, useClass: RouterStub },
|
||||
{ provide: 'objectElementProvider', useValue: (createdGridElementComponent) }
|
||||
{ provide: TruncatableService, useValue: truncatableServiceStub },
|
||||
{ provide: 'objectElementProvider', useValue: (mockCollectionWithAbstract) }
|
||||
],
|
||||
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
}).compileComponents(); // compile template and css
|
||||
}).overrideComponent(CollectionSearchResultGridElementComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(CollectionSearchResultGridElementComponent);
|
||||
collectionSearchResultGridElementComponent = fixture.componentInstance;
|
||||
}));
|
||||
|
||||
it('should show the item result cards in the grid element', () => {
|
||||
expect(fixture.debugElement.query(By.css('ds-collection-search-result-grid-element'))).toBeDefined();
|
||||
describe('When the collection has an abstract', () => {
|
||||
beforeEach(() => {
|
||||
collectionSearchResultGridElementComponent.dso = mockCollectionWithAbstract.dspaceObject;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the description paragraph', () => {
|
||||
const collectionAbstractField = fixture.debugElement.query(By.css('p.card-text'));
|
||||
expect(collectionAbstractField).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('should only show the description if "short description" metadata is present',() => {
|
||||
const descriptionText = expect(fixture.debugElement.query(By.css('p.card-text')));
|
||||
describe('When the collection has no abstract', () => {
|
||||
beforeEach(() => {
|
||||
collectionSearchResultGridElementComponent.dso = mockCollectionWithoutAbstract.dspaceObject;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
if (mockCollection.shortDescription.length > 0) {
|
||||
expect(descriptionText).toBeDefined();
|
||||
} else {
|
||||
expect(descriptionText).not.toBeDefined();
|
||||
}
|
||||
it('should not show the description paragraph', () => {
|
||||
const collectionAbstractField = fixture.debugElement.query(By.css('p.card-text'));
|
||||
expect(collectionAbstractField).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -2,7 +2,7 @@ import { Component } from '@angular/core';
|
||||
|
||||
import { renderElementsFor} from '../../../object-collection/shared/dso-element-decorator';
|
||||
|
||||
import { CollectionSearchResult } from './collection-search-result.model';
|
||||
import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
|
||||
import { SearchResultGridElementComponent } from '../search-result-grid-element.component';
|
||||
import { Collection } from '../../../../core/shared/collection.model';
|
||||
import { ViewMode } from '../../../../+search-page/search-options.model';
|
||||
|
@@ -1,5 +0,0 @@
|
||||
import { SearchResult } from '../../../../+search-page/search-result.model';
|
||||
import { Collection } from '../../../../core/shared/collection.model';
|
||||
|
||||
export class CollectionSearchResult extends SearchResult<Collection> {
|
||||
}
|
@@ -1,63 +1,83 @@
|
||||
import { CommunitySearchResultGridElementComponent } from './community-search-result-grid-element.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { RouterStub } from '../../../testing/router-stub';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TruncatePipe } from '../../../utils/truncate.pipe';
|
||||
import { Community } from '../../../../core/shared/community.model';
|
||||
import { TruncatableService } from '../../../truncatable/truncatable.service';
|
||||
import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';
|
||||
|
||||
let communitySearchResultGridElementComponent: CommunitySearchResultGridElementComponent;
|
||||
let fixture: ComponentFixture<CommunitySearchResultGridElementComponent>;
|
||||
const queryParam = 'test query';
|
||||
const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f';
|
||||
const activatedRouteStub = {
|
||||
queryParams: Observable.of({
|
||||
query: queryParam,
|
||||
scope: scopeParam
|
||||
})
|
||||
|
||||
const truncatableServiceStub: any = {
|
||||
isCollapsed: (id: number) => Observable.of(true),
|
||||
};
|
||||
const mockCommunity: Community = Object.assign(new Community(), {
|
||||
|
||||
const mockCommunityWithAbstract: CommunitySearchResult = new CommunitySearchResult();
|
||||
mockCommunityWithAbstract.hitHighlights = [];
|
||||
mockCommunityWithAbstract.dspaceObject = Object.assign(new Community(), {
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.description.abstract',
|
||||
language: 'en_US',
|
||||
value: 'Short description'
|
||||
} ]
|
||||
|
||||
});
|
||||
|
||||
const createdGridElementComponent: CommunitySearchResultGridElementComponent = new CommunitySearchResultGridElementComponent(mockCommunity);
|
||||
const mockCommunityWithoutAbstract: CommunitySearchResult = new CommunitySearchResult();
|
||||
mockCommunityWithoutAbstract.hitHighlights = [];
|
||||
mockCommunityWithoutAbstract.dspaceObject = Object.assign(new Community(), {
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
language: 'en_US',
|
||||
value: 'Test title'
|
||||
} ]
|
||||
});
|
||||
|
||||
describe('CommunitySearchResultGridElementComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CommunitySearchResultGridElementComponent, TruncatePipe ],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||
{ provide: Router, useClass: RouterStub },
|
||||
{ provide: 'objectElementProvider', useValue: (createdGridElementComponent) }
|
||||
{ provide: TruncatableService, useValue: truncatableServiceStub },
|
||||
{ provide: 'objectElementProvider', useValue: (mockCommunityWithAbstract) }
|
||||
],
|
||||
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
}).compileComponents(); // compile template and css
|
||||
}).overrideComponent(CommunitySearchResultGridElementComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(CommunitySearchResultGridElementComponent);
|
||||
communitySearchResultGridElementComponent = fixture.componentInstance;
|
||||
}));
|
||||
|
||||
it('should show the item result cards in the grid element', () => {
|
||||
expect(fixture.debugElement.query(By.css('ds-community-search-result-grid-element'))).toBeDefined();
|
||||
describe('When the community has an abstract', () => {
|
||||
beforeEach(() => {
|
||||
communitySearchResultGridElementComponent.dso = mockCommunityWithAbstract.dspaceObject;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the description paragraph', () => {
|
||||
const communityAbstractField = fixture.debugElement.query(By.css('p.card-text'));
|
||||
expect(communityAbstractField).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('should only show the description if "short description" metadata is present',() => {
|
||||
const descriptionText = expect(fixture.debugElement.query(By.css('p.card-text')));
|
||||
describe('When the community has no abstract', () => {
|
||||
beforeEach(() => {
|
||||
communitySearchResultGridElementComponent.dso = mockCommunityWithoutAbstract.dspaceObject;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
if (mockCommunity.shortDescription.length > 0) {
|
||||
expect(descriptionText).toBeDefined();
|
||||
} else {
|
||||
expect(descriptionText).not.toBeDefined();
|
||||
}
|
||||
it('should not show the description paragraph', () => {
|
||||
const communityAbstractField = fixture.debugElement.query(By.css('p.card-text'));
|
||||
expect(communityAbstractField).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { CommunitySearchResult } from './community-search-result.model';
|
||||
import { Community } from '../../../../core/shared/community.model';
|
||||
import { renderElementsFor } from '../../../object-collection/shared/dso-element-decorator';
|
||||
import { SearchResultGridElementComponent } from '../search-result-grid-element.component';
|
||||
import { ViewMode } from '../../../../+search-page/search-options.model';
|
||||
import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-community-search-result-grid-element',
|
||||
|
@@ -1,5 +0,0 @@
|
||||
import { SearchResult } from '../../../../+search-page/search-result.model';
|
||||
import { Community } from '../../../../core/shared/community.model';
|
||||
|
||||
export class CommunitySearchResult extends SearchResult<Community> {
|
||||
}
|
@@ -1,34 +1,33 @@
|
||||
<div class="card">
|
||||
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
|
||||
<ds-grid-thumbnail [thumbnail]="dso.getThumbnail()">
|
||||
</ds-grid-thumbnail>
|
||||
</a>
|
||||
<div class="card-body">
|
||||
<h4 class="card-title" [innerHTML]="dso.findMetadata('dc.title')"></h4>
|
||||
|
||||
<p *ngIf="dso.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])"
|
||||
class="item-authors card-text text-muted">
|
||||
<span
|
||||
*ngFor="let authorMd of dso.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let first=first;">
|
||||
<span *ngIf="first" [innerHTML]="authorMd.value">
|
||||
<span
|
||||
*ngIf="dso.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length>1">, ...</span>
|
||||
</span>
|
||||
</span>
|
||||
<span *ngIf="dso.findMetadata('dc.date.issued')"
|
||||
class="item-list-date">
|
||||
<span *ngIf="dso.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length>1">,</span>
|
||||
{{dso.findMetadata("dc.date.issued")}}</span>
|
||||
|
||||
|
||||
</p>
|
||||
<p class="item-abstract card-text" [innerHTML]="getFirstValue('dc.description.abstract') | dsTruncate:[200]">
|
||||
</p>
|
||||
|
||||
<div class="text-center">
|
||||
<a [routerLink]="['/items/' + dso.id]" class="lead btn btn-primary viewButton">View</a>
|
||||
<ds-truncatable [id]="dso.id">
|
||||
<div class="card mt-1" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
|
||||
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
|
||||
<div>
|
||||
<ds-grid-thumbnail [thumbnail]="dso.getThumbnail()">
|
||||
</ds-grid-thumbnail>
|
||||
</div>
|
||||
</a>
|
||||
<div class="card-body">
|
||||
<ds-truncatable-part [fixedHeight]="true" [id]="dso.id" [minLines]="3" type="h4">
|
||||
<h4 class="card-title" [innerHTML]="dso.findMetadata('dc.title')"></h4>
|
||||
</ds-truncatable-part>
|
||||
<p *ngIf="dso.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0"
|
||||
class="item-authors card-text text-muted">
|
||||
<ds-truncatable-part [fixedHeight]="true" [id]="dso.id" [minLines]="1">
|
||||
<span *ngIf="dso.findMetadata('dc.date.issued').length > 0" class="item-date">{{dso.findMetadata("dc.date.issued")}}</span>
|
||||
<span *ngFor="let authorMd of dso.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);">,
|
||||
<span [innerHTML]="authorMd.value"></span>
|
||||
</span>
|
||||
</ds-truncatable-part>
|
||||
</p>
|
||||
<p class="item-abstract card-text">
|
||||
<ds-truncatable-part [fixedHeight]="true" [id]="dso.id" [minLines]="3">
|
||||
<span [innerHTML]="getFirstValue('dc.description.abstract')"></span>
|
||||
</ds-truncatable-part>
|
||||
</p>
|
||||
<div class="text-center">
|
||||
<a [routerLink]="['/items/' + dso.id]"
|
||||
class="lead btn btn-primary viewButton">View</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</ds-truncatable>
|
@@ -1,2 +1,14 @@
|
||||
@import '../../../../../styles/variables';
|
||||
|
||||
.card {
|
||||
a > div {
|
||||
position: relative;
|
||||
.thumbnail-overlay {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
background-color: map-get($theme-colors, primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,24 +1,25 @@
|
||||
import { ItemSearchResultGridElementComponent } from './item-search-result-grid-element.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { RouterStub } from '../../../testing/router-stub';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { NO_ERRORS_SCHEMA, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TruncatePipe } from '../../../utils/truncate.pipe';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { TruncatableService } from '../../../truncatable/truncatable.service';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
|
||||
|
||||
let itemSearchResultGridElementComponent: ItemSearchResultGridElementComponent;
|
||||
let fixture: ComponentFixture<ItemSearchResultGridElementComponent>;
|
||||
const queryParam = 'test query';
|
||||
const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f';
|
||||
const activatedRouteStub = {
|
||||
queryParams: Observable.of({
|
||||
query: queryParam,
|
||||
scope: scopeParam
|
||||
})
|
||||
|
||||
const truncatableServiceStub: any = {
|
||||
isCollapsed: (id: number) => Observable.of(true),
|
||||
};
|
||||
const mockItem: Item = Object.assign(new Item(), {
|
||||
|
||||
const mockItemWithAuthorAndDate: ItemSearchResult = new ItemSearchResult();
|
||||
mockItemWithAuthorAndDate.hitHighlights = [];
|
||||
mockItemWithAuthorAndDate.dspaceObject = Object.assign(new Item(), {
|
||||
bitstreams: Observable.of({}),
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.contributor.author',
|
||||
@@ -28,53 +29,92 @@ const mockItem: Item = Object.assign(new Item(), {
|
||||
{
|
||||
key: 'dc.date.issued',
|
||||
language: null,
|
||||
value: '1650-06-26'
|
||||
value: '2015-06-26'
|
||||
}]
|
||||
});
|
||||
|
||||
const mockItemWithoutAuthorAndDate: ItemSearchResult = new ItemSearchResult();
|
||||
mockItemWithoutAuthorAndDate.hitHighlights = [];
|
||||
mockItemWithoutAuthorAndDate.dspaceObject = Object.assign(new Item(), {
|
||||
bitstreams: Observable.of({}),
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
language: 'en_US',
|
||||
value: 'This is just another title'
|
||||
},
|
||||
{
|
||||
key: 'dc.type',
|
||||
language: null,
|
||||
value: 'Article'
|
||||
}]
|
||||
});
|
||||
const createdGridElementComponent:ItemSearchResultGridElementComponent= new ItemSearchResultGridElementComponent(mockItem);
|
||||
|
||||
describe('ItemSearchResultGridElementComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ItemSearchResultGridElementComponent, TruncatePipe ],
|
||||
imports: [NoopAnimationsModule],
|
||||
declarations: [ItemSearchResultGridElementComponent, TruncatePipe],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||
{ provide: Router, useClass: RouterStub },
|
||||
{ provide: 'objectElementProvider', useValue: (createdGridElementComponent) }
|
||||
{ provide: TruncatableService, useValue: truncatableServiceStub },
|
||||
{ provide: 'objectElementProvider', useValue: (mockItemWithoutAuthorAndDate) }
|
||||
],
|
||||
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
}).compileComponents(); // compile template and css
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).overrideComponent(ItemSearchResultGridElementComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(ItemSearchResultGridElementComponent);
|
||||
itemSearchResultGridElementComponent = fixture.componentInstance;
|
||||
|
||||
}));
|
||||
|
||||
it('should show the item result cards in the grid element',() => {
|
||||
expect(fixture.debugElement.query(By.css('ds-item-search-result-grid-element'))).toBeDefined();
|
||||
describe('When the item has an author', () => {
|
||||
beforeEach(() => {
|
||||
itemSearchResultGridElementComponent.dso = mockItemWithAuthorAndDate.dspaceObject;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the author paragraph', () => {
|
||||
const itemAuthorField = fixture.debugElement.query(By.css('p.item-authors'));
|
||||
expect(itemAuthorField).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('should only show the author span if the author metadata is present',() => {
|
||||
const itemAuthorField = expect(fixture.debugElement.query(By.css('p.item-authors')));
|
||||
describe('When the item has no author', () => {
|
||||
beforeEach(() => {
|
||||
itemSearchResultGridElementComponent.dso = mockItemWithoutAuthorAndDate.dspaceObject;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
if (mockItem.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0) {
|
||||
expect(itemAuthorField).toBeDefined();
|
||||
} else {
|
||||
expect(itemAuthorField).not.toBeDefined();
|
||||
}
|
||||
it('should not show the author paragraph', () => {
|
||||
const itemAuthorField = fixture.debugElement.query(By.css('p.item-authors'));
|
||||
expect(itemAuthorField).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('should only show the date span if the issuedate is present',() => {
|
||||
const dateField = expect(fixture.debugElement.query(By.css('span.item-list-date')));
|
||||
describe('When the item has an issuedate', () => {
|
||||
beforeEach(() => {
|
||||
itemSearchResultGridElementComponent.dso = mockItemWithAuthorAndDate.dspaceObject;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
if (mockItem.findMetadata('dc.date.issued').length > 0) {
|
||||
expect(dateField).toBeDefined();
|
||||
} else {
|
||||
expect(dateField).not.toBeDefined();
|
||||
}
|
||||
it('should show the issuedate span', () => {
|
||||
const itemAuthorField = fixture.debugElement.query(By.css('span.item-date'));
|
||||
expect(itemAuthorField).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the item has no issuedate', () => {
|
||||
beforeEach(() => {
|
||||
itemSearchResultGridElementComponent.dso = mockItemWithoutAuthorAndDate.dspaceObject;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should not show the issuedate span', () => {
|
||||
const dateField = fixture.debugElement.query(By.css('span.item-date'));
|
||||
expect(dateField).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -5,11 +5,13 @@ import { SearchResultGridElementComponent } from '../search-result-grid-element.
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
|
||||
import { ViewMode } from '../../../../+search-page/search-options.model';
|
||||
import { focusShadow } from '../../../../shared/animations/focus';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-search-result-grid-element',
|
||||
styleUrls: ['../search-result-grid-element.component.scss', 'item-search-result-grid-element.component.scss'],
|
||||
templateUrl: 'item-search-result-grid-element.component.html'
|
||||
templateUrl: 'item-search-result-grid-element.component.html',
|
||||
animations: [focusShadow],
|
||||
})
|
||||
|
||||
@renderElementsFor(ItemSearchResult, ViewMode.Grid)
|
||||
|
@@ -6,6 +6,8 @@ import { Metadatum } from '../../../core/shared/metadatum.model';
|
||||
import { isEmpty, hasNoValue } from '../../empty.util';
|
||||
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
||||
import { ListableObject } from '../../object-collection/shared/listable-object.model';
|
||||
import { TruncatableService } from '../../truncatable/truncatable.service';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search-result-grid-element',
|
||||
@@ -15,8 +17,8 @@ import { ListableObject } from '../../object-collection/shared/listable-object.m
|
||||
export class SearchResultGridElementComponent<T extends SearchResult<K>, K extends DSpaceObject> extends AbstractListableElementComponent<T> {
|
||||
dso: K;
|
||||
|
||||
public constructor(@Inject('objectElementProvider') public gridable: ListableObject) {
|
||||
super(gridable);
|
||||
public constructor(@Inject('objectElementProvider') public listableObject: ListableObject, private truncatableService: TruncatableService) {
|
||||
super(listableObject);
|
||||
this.dso = this.object.dspaceObject;
|
||||
}
|
||||
|
||||
@@ -44,7 +46,7 @@ export class SearchResultGridElementComponent<T extends SearchResult<K>, K exten
|
||||
this.object.hitHighlights.some(
|
||||
(md: Metadatum) => {
|
||||
if (key === md.key) {
|
||||
result = md.value;
|
||||
result = md.value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -54,4 +56,8 @@ export class SearchResultGridElementComponent<T extends SearchResult<K>, K exten
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
isCollapsed(): Observable<boolean> {
|
||||
return this.truncatableService.isCollapsed(this.dso.id);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<a [routerLink]="['/collections/' + object.id]" class="lead">
|
||||
{{object.name}}
|
||||
</a>
|
||||
<div *ngIf="object.shortDescription" class="text-muted">
|
||||
<div *ngIf="object.shortDescription" class="text-muted abstract-text">
|
||||
{{object.shortDescription}}
|
||||
</div>
|
||||
|
@@ -0,0 +1,70 @@
|
||||
import { CollectionListElementComponent } from './collection-list-element.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
|
||||
let collectionListElementComponent: CollectionListElementComponent;
|
||||
let fixture: ComponentFixture<CollectionListElementComponent>;
|
||||
|
||||
const mockCollectionWithAbstract: Collection = Object.assign(new Collection(), {
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.description.abstract',
|
||||
language: 'en_US',
|
||||
value: 'Short description'
|
||||
}]
|
||||
});
|
||||
|
||||
const mockCollectionWithoutAbstract: Collection = Object.assign(new Collection(), {
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
language: 'en_US',
|
||||
value: 'Test title'
|
||||
}]
|
||||
});
|
||||
|
||||
describe('CollectionListElementComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CollectionListElementComponent ],
|
||||
providers: [
|
||||
{ provide: 'objectElementProvider', useValue: (mockCollectionWithAbstract)}
|
||||
],
|
||||
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
}).overrideComponent(CollectionListElementComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(CollectionListElementComponent);
|
||||
collectionListElementComponent = fixture.componentInstance;
|
||||
}));
|
||||
|
||||
describe('When the collection has an abstract', () => {
|
||||
beforeEach(() => {
|
||||
collectionListElementComponent.object = mockCollectionWithAbstract;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the description paragraph', () => {
|
||||
const collectionAbstractField = fixture.debugElement.query(By.css('div.abstract-text'));
|
||||
expect(collectionAbstractField).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the collection has no abstract', () => {
|
||||
beforeEach(() => {
|
||||
collectionListElementComponent.object = mockCollectionWithoutAbstract;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should not show the description paragraph', () => {
|
||||
const collectionAbstractField = fixture.debugElement.query(By.css('div.abstract-text'));
|
||||
expect(collectionAbstractField).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,6 +1,6 @@
|
||||
<a [routerLink]="['/communities/' + object.id]" class="lead">
|
||||
{{object.name}}
|
||||
</a>
|
||||
<div *ngIf="object.shortDescription" class="text-muted">
|
||||
<div *ngIf="object.shortDescription" class="text-muted abstract-text">
|
||||
{{object.shortDescription}}
|
||||
</div>
|
||||
|
@@ -0,0 +1,70 @@
|
||||
import { CommunityListElementComponent } from './community-list-element.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Community } from '../../../core/shared/community.model';
|
||||
|
||||
let communityListElementComponent: CommunityListElementComponent;
|
||||
let fixture: ComponentFixture<CommunityListElementComponent>;
|
||||
|
||||
const mockCommunityWithAbstract: Community = Object.assign(new Community(), {
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.description.abstract',
|
||||
language: 'en_US',
|
||||
value: 'Short description'
|
||||
}]
|
||||
});
|
||||
|
||||
const mockCommunityWithoutAbstract: Community = Object.assign(new Community(), {
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
language: 'en_US',
|
||||
value: 'Test title'
|
||||
}]
|
||||
});
|
||||
|
||||
describe('CommunityListElementComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CommunityListElementComponent ],
|
||||
providers: [
|
||||
{ provide: 'objectElementProvider', useValue: (mockCommunityWithAbstract)}
|
||||
],
|
||||
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
}).overrideComponent(CommunityListElementComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(CommunityListElementComponent);
|
||||
communityListElementComponent = fixture.componentInstance;
|
||||
}));
|
||||
|
||||
describe('When the community has an abstract', () => {
|
||||
beforeEach(() => {
|
||||
communityListElementComponent.object = mockCommunityWithAbstract;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the description paragraph', () => {
|
||||
const communityAbstractField = fixture.debugElement.query(By.css('div.abstract-text'));
|
||||
expect(communityAbstractField).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the community has no abstract', () => {
|
||||
beforeEach(() => {
|
||||
communityListElementComponent.object = mockCommunityWithoutAbstract;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should not show the description paragraph', () => {
|
||||
const communityAbstractField = fixture.debugElement.query(By.css('div.abstract-text'));
|
||||
expect(communityAbstractField).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
@@ -3,12 +3,16 @@
|
||||
</a>
|
||||
<div>
|
||||
<span class="text-muted">
|
||||
<span *ngIf="object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);" class="item-list-authors">
|
||||
<span *ngIf="object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0"
|
||||
class="item-list-authors">
|
||||
<span *ngFor="let authorMd of object.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">{{authorMd.value}}
|
||||
<span *ngIf="!last">; </span>
|
||||
</span>
|
||||
</span>
|
||||
(<span *ngIf="object.findMetadata('dc.publisher')" class="item-list-publisher">{{object.findMetadata("dc.publisher")}}, </span><span *ngIf="object.findMetadata('dc.date.issued')" class="item-list-date">{{object.findMetadata("dc.date.issued")}}</span>)
|
||||
(<span *ngIf="object.findMetadata('dc.publisher')" class="item-list-publisher">{{object.findMetadata("dc.publisher")}}, </span><span
|
||||
*ngIf="object.findMetadata('dc.date.issued')" class="item-list-date">{{object.findMetadata("dc.date.issued")}}</span>)
|
||||
</span>
|
||||
<div *ngIf="object.findMetadata('dc.description.abstract')" class="item-list-abstract">{{object.findMetadata("dc.description.abstract") | dsTruncate:[200] }}</div>
|
||||
<div *ngIf="object.findMetadata('dc.description.abstract')" class="item-list-abstract">
|
||||
{{object.findMetadata("dc.description.abstract") | dsTruncate:[200] }}
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -0,0 +1,108 @@
|
||||
import { ItemListElementComponent } from './item-list-element.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TruncatePipe } from '../../utils/truncate.pipe';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
let itemListElementComponent: ItemListElementComponent;
|
||||
let fixture: ComponentFixture<ItemListElementComponent>;
|
||||
|
||||
const mockItemWithAuthorAndDate: Item = Object.assign(new Item(), {
|
||||
bitstreams: Observable.of({}),
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.contributor.author',
|
||||
language: 'en_US',
|
||||
value: 'Smith, Donald'
|
||||
},
|
||||
{
|
||||
key: 'dc.date.issued',
|
||||
language: null,
|
||||
value: '2015-06-26'
|
||||
}]
|
||||
});
|
||||
const mockItemWithoutAuthorAndDate: Item = Object.assign(new Item(), {
|
||||
bitstreams: Observable.of({}),
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
language: 'en_US',
|
||||
value: 'This is just another title'
|
||||
},
|
||||
{
|
||||
key: 'dc.type',
|
||||
language: null,
|
||||
value: 'Article'
|
||||
}]
|
||||
});
|
||||
|
||||
describe('ItemListElementComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ItemListElementComponent , TruncatePipe],
|
||||
providers: [
|
||||
{ provide: 'objectElementProvider', useValue: {mockItemWithAuthorAndDate}}
|
||||
],
|
||||
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
}).overrideComponent(ItemListElementComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(ItemListElementComponent);
|
||||
itemListElementComponent = fixture.componentInstance;
|
||||
|
||||
}));
|
||||
|
||||
describe('When the item has an author', () => {
|
||||
beforeEach(() => {
|
||||
itemListElementComponent.object = mockItemWithAuthorAndDate;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the author paragraph', () => {
|
||||
const itemAuthorField = fixture.debugElement.query(By.css('span.item-list-authors'));
|
||||
expect(itemAuthorField).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the item has no author', () => {
|
||||
beforeEach(() => {
|
||||
itemListElementComponent.object = mockItemWithoutAuthorAndDate;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should not show the author paragraph', () => {
|
||||
const itemAuthorField = fixture.debugElement.query(By.css('span.item-list-authors'));
|
||||
expect(itemAuthorField).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the item has an issuedate', () => {
|
||||
beforeEach(() => {
|
||||
itemListElementComponent.object = mockItemWithAuthorAndDate;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the issuedate span', () => {
|
||||
const itemAuthorField = fixture.debugElement.query(By.css('span.item-list-date'));
|
||||
expect(itemAuthorField).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the item has no issuedate', () => {
|
||||
beforeEach(() => {
|
||||
itemListElementComponent.object = mockItemWithoutAuthorAndDate;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should not show the issuedate span', () => {
|
||||
const dateField = fixture.debugElement.query(By.css('span.item-list-date'));
|
||||
expect(dateField).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
@@ -10,8 +10,8 @@
|
||||
(sortDirectionChange)="onSortDirectionChange($event)"
|
||||
(sortFieldChange)="onSortFieldChange($event)"
|
||||
(paginationChange)="onPaginationChange($event)">
|
||||
<ul *ngIf="objects?.hasSucceeded"> <!--class="list-unstyled"-->
|
||||
<li *ngFor="let object of objects?.payload?.page">
|
||||
<ul *ngIf="objects?.hasSucceeded" class="list-unstyled">
|
||||
<li *ngFor="let object of objects?.payload?.page" class="mt-4 mb-4">
|
||||
<ds-wrapper-list-element [object]="object"></ds-wrapper-list-element>
|
||||
</li>
|
||||
</ul>
|
||||
|
@@ -1 +1 @@
|
||||
@import '../../../styles/variables';
|
||||
@import '../../../styles/variables';
|
@@ -1,2 +1,2 @@
|
||||
<a [routerLink]="['/collections/' + dso.id]" class="lead" [innerHTML]="getFirstValue('dc.title')"></a>
|
||||
<div *ngIf="dso.shortDescription" class="text-muted" [innerHTML]="getFirstValue('dc.description.abstract')"></div>
|
||||
<div *ngIf="dso.shortDescription" class="text-muted abstract-text" [innerHTML]="getFirstValue('dc.description.abstract')"></div>
|
||||
|
@@ -0,0 +1,83 @@
|
||||
import { CollectionSearchResultListElementComponent } from './collection-search-result-list-element.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TruncatePipe } from '../../../utils/truncate.pipe';
|
||||
import { Collection } from '../../../../core/shared/collection.model';
|
||||
import { TruncatableService } from '../../../truncatable/truncatable.service';
|
||||
import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
|
||||
|
||||
let collectionSearchResultListElementComponent: CollectionSearchResultListElementComponent;
|
||||
let fixture: ComponentFixture<CollectionSearchResultListElementComponent>;
|
||||
|
||||
const truncatableServiceStub: any = {
|
||||
isCollapsed: (id: number) => Observable.of(true),
|
||||
};
|
||||
|
||||
const mockCollectionWithAbstract: CollectionSearchResult = new CollectionSearchResult();
|
||||
mockCollectionWithAbstract.hitHighlights = [];
|
||||
mockCollectionWithAbstract.dspaceObject = Object.assign(new Collection(), {
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.description.abstract',
|
||||
language: 'en_US',
|
||||
value: 'Short description'
|
||||
} ]
|
||||
});
|
||||
|
||||
const mockCollectionWithoutAbstract: CollectionSearchResult = new CollectionSearchResult();
|
||||
mockCollectionWithoutAbstract.hitHighlights = [];
|
||||
mockCollectionWithoutAbstract.dspaceObject = Object.assign(new Collection(), {
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
language: 'en_US',
|
||||
value: 'Test title'
|
||||
} ]
|
||||
});
|
||||
|
||||
describe('CollectionSearchResultListElementComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CollectionSearchResultListElementComponent, TruncatePipe ],
|
||||
providers: [
|
||||
{ provide: TruncatableService, useValue: truncatableServiceStub },
|
||||
{ provide: 'objectElementProvider', useValue: (mockCollectionWithAbstract) }
|
||||
],
|
||||
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
}).overrideComponent(CollectionSearchResultListElementComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(CollectionSearchResultListElementComponent);
|
||||
collectionSearchResultListElementComponent = fixture.componentInstance;
|
||||
}));
|
||||
|
||||
describe('When the collection has an abstract', () => {
|
||||
beforeEach(() => {
|
||||
collectionSearchResultListElementComponent.dso = mockCollectionWithAbstract.dspaceObject;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the description paragraph', () => {
|
||||
const collectionAbstractField = fixture.debugElement.query(By.css('div.abstract-text'));
|
||||
expect(collectionAbstractField).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the collection has no abstract', () => {
|
||||
beforeEach(() => {
|
||||
collectionSearchResultListElementComponent.dso = mockCollectionWithoutAbstract.dspaceObject;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should not show the description paragraph', () => {
|
||||
const collectionAbstractField = fixture.debugElement.query(By.css('div.abstract-text'));
|
||||
expect(collectionAbstractField).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,10 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { renderElementsFor } from '../../../object-collection/shared/dso-element-decorator';
|
||||
import { CollectionSearchResult } from './collection-search-result.model';
|
||||
import { SearchResultListElementComponent } from '../search-result-list-element.component';
|
||||
import { Collection } from '../../../../core/shared/collection.model';
|
||||
import { ViewMode } from '../../../../+search-page/search-options.model';
|
||||
import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-collection-search-result-list-element',
|
||||
|
@@ -1,5 +0,0 @@
|
||||
import { SearchResult } from '../../../../+search-page/search-result.model';
|
||||
import { Collection } from '../../../../core/shared/collection.model';
|
||||
|
||||
export class CollectionSearchResult extends SearchResult<Collection> {
|
||||
}
|
@@ -1,2 +1,2 @@
|
||||
<a [routerLink]="['/communities/' + dso.id]" class="lead" [innerHTML]="getFirstValue('dc.title')"></a>
|
||||
<div *ngIf="dso.shortDescription" class="text-muted" [innerHTML]="getFirstValue('dc.description.abstract')"></div>
|
||||
<div *ngIf="dso.shortDescription" class="text-muted abstract-text" [innerHTML]="getFirstValue('dc.description.abstract')"></div>
|
||||
|
@@ -0,0 +1,83 @@
|
||||
import { CommunitySearchResultListElementComponent } from './community-search-result-list-element.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TruncatePipe } from '../../../utils/truncate.pipe';
|
||||
import { Community } from '../../../../core/shared/community.model';
|
||||
import { TruncatableService } from '../../../truncatable/truncatable.service';
|
||||
import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';
|
||||
|
||||
let communitySearchResultListElementComponent: CommunitySearchResultListElementComponent;
|
||||
let fixture: ComponentFixture<CommunitySearchResultListElementComponent>;
|
||||
|
||||
const truncatableServiceStub: any = {
|
||||
isCollapsed: (id: number) => Observable.of(true),
|
||||
};
|
||||
|
||||
const mockCommunityWithAbstract: CommunitySearchResult = new CommunitySearchResult();
|
||||
mockCommunityWithAbstract.hitHighlights = [];
|
||||
mockCommunityWithAbstract.dspaceObject = Object.assign(new Community(), {
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.description.abstract',
|
||||
language: 'en_US',
|
||||
value: 'Short description'
|
||||
} ]
|
||||
});
|
||||
|
||||
const mockCommunityWithoutAbstract: CommunitySearchResult = new CommunitySearchResult();
|
||||
mockCommunityWithoutAbstract.hitHighlights = [];
|
||||
mockCommunityWithoutAbstract.dspaceObject = Object.assign(new Community(), {
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
language: 'en_US',
|
||||
value: 'Test title'
|
||||
} ]
|
||||
});
|
||||
|
||||
describe('CommunitySearchResultListElementComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CommunitySearchResultListElementComponent, TruncatePipe ],
|
||||
providers: [
|
||||
{ provide: TruncatableService, useValue: truncatableServiceStub },
|
||||
{ provide: 'objectElementProvider', useValue: (mockCommunityWithAbstract) }
|
||||
],
|
||||
|
||||
schemas: [ NO_ERRORS_SCHEMA ]
|
||||
}).overrideComponent(CommunitySearchResultListElementComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(CommunitySearchResultListElementComponent);
|
||||
communitySearchResultListElementComponent = fixture.componentInstance;
|
||||
}));
|
||||
|
||||
describe('When the community has an abstract', () => {
|
||||
beforeEach(() => {
|
||||
communitySearchResultListElementComponent.dso = mockCommunityWithAbstract.dspaceObject;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the description paragraph', () => {
|
||||
const communityAbstractField = fixture.debugElement.query(By.css('div.abstract-text'));
|
||||
expect(communityAbstractField).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the community has no abstract', () => {
|
||||
beforeEach(() => {
|
||||
communitySearchResultListElementComponent.dso = mockCommunityWithoutAbstract.dspaceObject;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should not show the description paragraph', () => {
|
||||
const communityAbstractField = fixture.debugElement.query(By.css('div.abstract-text'));
|
||||
expect(communityAbstractField).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,7 +1,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { renderElementsFor } from '../../../object-collection/shared/dso-element-decorator';
|
||||
import { CommunitySearchResult } from './community-search-result.model';
|
||||
import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';
|
||||
import { SearchResultListElementComponent } from '../search-result-list-element.component';
|
||||
import { Community } from '../../../../core/shared/community.model';
|
||||
import { ViewMode } from '../../../../+search-page/search-options.model';
|
||||
|
@@ -1,5 +0,0 @@
|
||||
import { SearchResult } from '../../../../+search-page/search-result.model';
|
||||
import { Community } from '../../../../core/shared/community.model';
|
||||
|
||||
export class CommunitySearchResult extends SearchResult<Community> {
|
||||
}
|
@@ -1,12 +1,24 @@
|
||||
<a [routerLink]="['/items/' + dso.id]" class="lead" [innerHTML]="getFirstValue('dc.title')"></a>
|
||||
<div>
|
||||
<ds-truncatable [id]="dso.id">
|
||||
<a
|
||||
[routerLink]="['/items/' + dso.id]" class="lead"
|
||||
[innerHTML]="getFirstValue('dc.title')"></a>
|
||||
<span class="text-muted">
|
||||
<span *ngIf="dso.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']);" class="item-list-authors">
|
||||
<span *ngFor="let author of getValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">
|
||||
<span [innerHTML]="author"><span [innerHTML]="author"></span></span>
|
||||
</span>
|
||||
</span>
|
||||
(<span *ngIf="dso.findMetadata('dc.publisher')" class="item-list-publisher" [innerHTML]="getFirstValue('dc.publisher')">, </span><span *ngIf="dso.findMetadata('dc.date.issued')" class="item-list-date" [innerHTML]="getFirstValue('dc.date.issued')"></span>)
|
||||
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
||||
(<span *ngIf="dso.findMetadata('dc.publisher')" class="item-list-publisher"
|
||||
[innerHTML]="getFirstValue('dc.publisher')">, </span><span
|
||||
*ngIf="dso.findMetadata('dc.date.issued')" class="item-list-date"
|
||||
[innerHTML]="getFirstValue('dc.date.issued')"></span>)
|
||||
<span *ngIf="dso.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0"
|
||||
class="item-list-authors">
|
||||
<span *ngFor="let author of getValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); let last=last;">
|
||||
<span [innerHTML]="author"><span [innerHTML]="author"></span></span>
|
||||
</span>
|
||||
</span>
|
||||
</ds-truncatable-part>
|
||||
</span>
|
||||
<div *ngIf="dso.findMetadata('dc.description.abstract')" class="item-list-abstract" [innerHTML]="getFirstValue('dc.description.abstract') | dsTruncate:[200]"></div>
|
||||
</div>
|
||||
<div *ngIf="dso.findMetadata('dc.description.abstract')" class="item-list-abstract">
|
||||
<ds-truncatable-part [id]="dso.id" [minLines]="3"><span
|
||||
[innerHTML]="getFirstValue('dc.description.abstract')"></span>
|
||||
</ds-truncatable-part>
|
||||
</div>
|
||||
</ds-truncatable>
|
@@ -0,0 +1,120 @@
|
||||
import { ItemSearchResultListElementComponent } from './item-search-result-list-element.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { NO_ERRORS_SCHEMA, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TruncatePipe } from '../../../utils/truncate.pipe';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { TruncatableService } from '../../../truncatable/truncatable.service';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
|
||||
|
||||
let itemSearchResultListElementComponent: ItemSearchResultListElementComponent;
|
||||
let fixture: ComponentFixture<ItemSearchResultListElementComponent>;
|
||||
|
||||
const truncatableServiceStub: any = {
|
||||
isCollapsed: (id: number) => Observable.of(true),
|
||||
};
|
||||
|
||||
const mockItemWithAuthorAndDate: ItemSearchResult = new ItemSearchResult();
|
||||
mockItemWithAuthorAndDate.hitHighlights = [];
|
||||
mockItemWithAuthorAndDate.dspaceObject = Object.assign(new Item(), {
|
||||
bitstreams: Observable.of({}),
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.contributor.author',
|
||||
language: 'en_US',
|
||||
value: 'Smith, Donald'
|
||||
},
|
||||
{
|
||||
key: 'dc.date.issued',
|
||||
language: null,
|
||||
value: '2015-06-26'
|
||||
}]
|
||||
});
|
||||
|
||||
const mockItemWithoutAuthorAndDate: ItemSearchResult = new ItemSearchResult();
|
||||
mockItemWithoutAuthorAndDate.hitHighlights = [];
|
||||
mockItemWithoutAuthorAndDate.dspaceObject = Object.assign(new Item(), {
|
||||
bitstreams: Observable.of({}),
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
language: 'en_US',
|
||||
value: 'This is just another title'
|
||||
},
|
||||
{
|
||||
key: 'dc.type',
|
||||
language: null,
|
||||
value: 'Article'
|
||||
}]
|
||||
});
|
||||
|
||||
describe('ItemSearchResultListElementComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [NoopAnimationsModule],
|
||||
declarations: [ItemSearchResultListElementComponent, TruncatePipe],
|
||||
providers: [
|
||||
{ provide: TruncatableService, useValue: truncatableServiceStub },
|
||||
{ provide: 'objectElementProvider', useValue: (mockItemWithoutAuthorAndDate) }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).overrideComponent(ItemSearchResultListElementComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(ItemSearchResultListElementComponent);
|
||||
itemSearchResultListElementComponent = fixture.componentInstance;
|
||||
}));
|
||||
|
||||
describe('When the item has an author', () => {
|
||||
beforeEach(() => {
|
||||
itemSearchResultListElementComponent.dso = mockItemWithAuthorAndDate.dspaceObject;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the author paragraph', () => {
|
||||
const itemAuthorField = fixture.debugElement.query(By.css('span.item-list-authors'));
|
||||
expect(itemAuthorField).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the item has no author', () => {
|
||||
beforeEach(() => {
|
||||
itemSearchResultListElementComponent.dso = mockItemWithoutAuthorAndDate.dspaceObject;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should not show the author paragraph', () => {
|
||||
const itemAuthorField = fixture.debugElement.query(By.css('span.item-list-authors'));
|
||||
expect(itemAuthorField).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the item has an issuedate', () => {
|
||||
beforeEach(() => {
|
||||
itemSearchResultListElementComponent.dso = mockItemWithAuthorAndDate.dspaceObject;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the issuedate span', () => {
|
||||
const itemAuthorField = fixture.debugElement.query(By.css('span.item-list-date'));
|
||||
expect(itemAuthorField).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the item has no issuedate', () => {
|
||||
beforeEach(() => {
|
||||
itemSearchResultListElementComponent.dso = mockItemWithoutAuthorAndDate.dspaceObject;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should not show the issuedate span', () => {
|
||||
const dateField = fixture.debugElement.query(By.css('span.item-list-date'));
|
||||
expect(dateField).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,16 +1,21 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, Inject, OnInit } from '@angular/core';
|
||||
|
||||
import { renderElementsFor } from '../../../object-collection/shared/dso-element-decorator';
|
||||
import { SearchResultListElementComponent } from '../search-result-list-element.component';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
|
||||
import { ViewMode } from '../../../../+search-page/search-options.model';
|
||||
import { ListableObject } from '../../../object-collection/shared/listable-object.model';
|
||||
import { focusBackground } from '../../../animations/focus';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-search-result-list-element',
|
||||
styleUrls: ['../search-result-list-element.component.scss', 'item-search-result-list-element.component.scss'],
|
||||
templateUrl: 'item-search-result-list-element.component.html'
|
||||
templateUrl: 'item-search-result-list-element.component.html',
|
||||
animations: [focusBackground],
|
||||
|
||||
})
|
||||
|
||||
@renderElementsFor(ItemSearchResult, ViewMode.List)
|
||||
export class ItemSearchResultListElementComponent extends SearchResultListElementComponent<ItemSearchResult, Item> {}
|
||||
export class ItemSearchResultListElementComponent extends SearchResultListElementComponent<ItemSearchResult, Item> {
|
||||
}
|
||||
|
@@ -6,6 +6,8 @@ import { Metadatum } from '../../../core/shared/metadatum.model';
|
||||
import { isEmpty, hasNoValue } from '../../empty.util';
|
||||
import { ListableObject } from '../../object-collection/shared/listable-object.model';
|
||||
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { TruncatableService } from '../../truncatable/truncatable.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search-result-list-element',
|
||||
@@ -15,7 +17,7 @@ import { AbstractListableElementComponent } from '../../object-collection/shared
|
||||
export class SearchResultListElementComponent<T extends SearchResult<K>, K extends DSpaceObject> extends AbstractListableElementComponent<T> {
|
||||
dso: K;
|
||||
|
||||
public constructor(@Inject('objectElementProvider') public listable: ListableObject) {
|
||||
public constructor(@Inject('objectElementProvider') public listable: ListableObject, private truncatableService: TruncatableService) {
|
||||
super(listable);
|
||||
this.dso = this.object.dspaceObject;
|
||||
}
|
||||
@@ -54,4 +56,8 @@ export class SearchResultListElementComponent<T extends SearchResult<K>, K exten
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
isCollapsed(): Observable<boolean> {
|
||||
return this.truncatableService.isCollapsed(this.dso.id);
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)" class="row">
|
||||
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)" class="row" action="/search">
|
||||
<div *ngIf="isNotEmpty(scopes)" class="col-12 col-sm-3">
|
||||
<select [(ngModel)]="selectedId" name="scope" class="form-control" aria-label="Search scope" [compareWith]="byId">
|
||||
<option value>{{'search.form.search_dspace' | translate}}</option>
|
||||
@@ -8,7 +8,7 @@
|
||||
<div [ngClass]="{'col-sm-9': isNotEmpty(scopes)}" class="col-12">
|
||||
<div class="form-group input-group">
|
||||
<input type="text" [(ngModel)]="query" name="query" class="form-control" aria-label="Search input">
|
||||
<span class="input-group-btn">
|
||||
<span class="input-group-append">
|
||||
<button type="submit" class="search-button btn btn-secondary">{{ ('search.form.search' | translate) }}</button>
|
||||
</span>
|
||||
</div>
|
||||
|
@@ -2,7 +2,6 @@ import { Component, Input } from '@angular/core';
|
||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
import { Router } from '@angular/router';
|
||||
import { isNotEmpty, hasValue, isEmpty } from '../empty.util';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
|
@@ -12,7 +12,6 @@ import { NgxPaginationModule } from 'ngx-pagination';
|
||||
import { EnumKeysPipe } from './utils/enum-keys-pipe';
|
||||
import { FileSizePipe } from './utils/file-size-pipe';
|
||||
import { SafeUrlPipe } from './utils/safe-url-pipe';
|
||||
import { TruncatePipe } from './utils/truncate.pipe';
|
||||
|
||||
import { CollectionListElementComponent } from './object-list/collection-list-element/collection-list-element.component';
|
||||
import { CommunityListElementComponent } from './object-list/community-list-element/community-list-element.component';
|
||||
@@ -43,6 +42,11 @@ import { GridThumbnailComponent } from './object-grid/grid-thumbnail/grid-thumbn
|
||||
import { VarDirective } from './utils/var.directive';
|
||||
import { NotificationComponent } from './notifications/notification/notification.component';
|
||||
import { NotificationsBoardComponent } from './notifications/notifications-board/notifications-board.component';
|
||||
import { DragClickDirective } from './utils/drag-click.directive';
|
||||
import { TruncatePipe } from './utils/truncate.pipe';
|
||||
import { TruncatableComponent } from './truncatable/truncatable.component';
|
||||
import { TruncatableService } from './truncatable/truncatable.service';
|
||||
import { TruncatablePartComponent } from './truncatable/truncatable-part/truncatable-part.component';
|
||||
|
||||
const MODULES = [
|
||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||
@@ -82,8 +86,8 @@ const COMPONENTS = [
|
||||
GridThumbnailComponent,
|
||||
WrapperListElementComponent,
|
||||
ViewModeSwitchComponent,
|
||||
// NotificationComponent,
|
||||
// NotificationsBoardComponent
|
||||
TruncatableComponent,
|
||||
TruncatablePartComponent,
|
||||
];
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
@@ -96,11 +100,15 @@ const ENTRY_COMPONENTS = [
|
||||
CollectionGridElementComponent,
|
||||
CommunityGridElementComponent,
|
||||
SearchResultGridElementComponent,
|
||||
// NotificationComponent,
|
||||
];
|
||||
|
||||
const PROVIDERS = [
|
||||
TruncatableService
|
||||
];
|
||||
|
||||
const DIRECTIVES = [
|
||||
VarDirective
|
||||
VarDirective,
|
||||
DragClickDirective
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
@@ -114,6 +122,9 @@ const DIRECTIVES = [
|
||||
...ENTRY_COMPONENTS,
|
||||
...DIRECTIVES
|
||||
],
|
||||
providers: [
|
||||
...PROVIDERS
|
||||
],
|
||||
exports: [
|
||||
...MODULES,
|
||||
...PIPES,
|
||||
|
@@ -0,0 +1,5 @@
|
||||
<div class="clamp-{{lines}} min-{{minLines}} {{type}} {{fixedHeight ? 'fixedHeight' : ''}}">
|
||||
<div class="content">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,63 @@
|
||||
@import '../../../../styles/variables';
|
||||
@import '../../../../styles/mixins';
|
||||
|
||||
@mixin clamp($lines, $size-factor: 1, $line-height: $line-height-base) {
|
||||
$height: $line-height * $font-size-base * $size-factor;
|
||||
&.fixedHeight {
|
||||
height: $lines * $height;
|
||||
}
|
||||
.content {
|
||||
max-height: $lines * $height;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
line-height: $line-height;
|
||||
overflow-wrap: break-word;
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
padding-right: 15px;
|
||||
top: ($lines - 1) * $height;
|
||||
right: 0;
|
||||
width: 30%;
|
||||
min-width: 75px;
|
||||
max-width: 150px;
|
||||
height: $height;
|
||||
background: linear-gradient(to right, rgba(255, 255, 255, 0), $body-bg 70%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin min($lines, $size-factor: 1, $line-height: $line-height-base) {
|
||||
$height: $line-height * $font-size-base * $size-factor;
|
||||
min-height: $lines * $height;
|
||||
}
|
||||
|
||||
$h4-factor: strip-unit($h4-font-size);
|
||||
@for $i from 1 through 15 {
|
||||
.clamp-#{$i} {
|
||||
transition: height 1s;
|
||||
@include clamp($i);
|
||||
&.title {
|
||||
@include clamp($i, 1.25);
|
||||
}
|
||||
&.h4 {
|
||||
@include clamp($i, $h4-factor, $headings-line-height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.clamp-none {
|
||||
overflow: hidden;
|
||||
@for $i from 1 through 15 {
|
||||
&.fixedHeight.min-#{$i} {
|
||||
transition: height 1s;
|
||||
@include min($i);
|
||||
&.title {
|
||||
@include min($i, 1.25);
|
||||
}
|
||||
&.h4 {
|
||||
@include min($i, $h4-factor, $headings-line-height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { TruncatablePartComponent } from './truncatable-part.component';
|
||||
import { TruncatableService } from '../truncatable.service';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
describe('TruncatablePartComponent', () => {
|
||||
let comp: TruncatablePartComponent;
|
||||
let fixture: ComponentFixture<TruncatablePartComponent>;
|
||||
const id1 = '123';
|
||||
const id2 = '456';
|
||||
|
||||
let truncatableService;
|
||||
const truncatableServiceStub: any = {
|
||||
isCollapsed: (id: string) => {
|
||||
if (id === id1) {
|
||||
return Observable.of(true)
|
||||
} else {
|
||||
return Observable.of(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [NoopAnimationsModule],
|
||||
declarations: [TruncatablePartComponent],
|
||||
providers: [
|
||||
{ provide: TruncatableService, useValue: truncatableServiceStub },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).overrideComponent(TruncatablePartComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
}).compileComponents();
|
||||
}));
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TruncatablePartComponent);
|
||||
comp = fixture.componentInstance; // TruncatablePartComponent test instance
|
||||
fixture.detectChanges();
|
||||
truncatableService = (comp as any).filterService;
|
||||
});
|
||||
|
||||
describe('When the item is collapsed', () => {
|
||||
beforeEach(() => {
|
||||
comp.id = id1;
|
||||
comp.minLines = 5;
|
||||
(comp as any).setLines();
|
||||
fixture.detectChanges();
|
||||
})
|
||||
;
|
||||
|
||||
it('lines should equal minlines', () => {
|
||||
expect((comp as any).lines).toEqual(comp.minLines.toString());
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the item is expanded', () => {
|
||||
beforeEach(() => {
|
||||
comp.id = id2;
|
||||
})
|
||||
;
|
||||
|
||||
it('lines should equal maxlines when maxlines has a value', () => {
|
||||
comp.maxLines = 5;
|
||||
(comp as any).setLines();
|
||||
fixture.detectChanges();
|
||||
expect((comp as any).lines).toEqual(comp.maxLines.toString());
|
||||
});
|
||||
|
||||
it('lines should equal \'none\' when maxlines has no value', () => {
|
||||
(comp as any).setLines();
|
||||
fixture.detectChanges();
|
||||
expect((comp as any).lines).toEqual('none');
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,39 @@
|
||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { TruncatableService } from '../truncatable.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-truncatable-part',
|
||||
templateUrl: './truncatable-part.component.html',
|
||||
styleUrls: ['./truncatable-part.component.scss']
|
||||
})
|
||||
|
||||
export class TruncatablePartComponent implements OnInit, OnDestroy {
|
||||
@Input() minLines: number;
|
||||
@Input() maxLines = -1;
|
||||
@Input() id: string;
|
||||
@Input() type: string;
|
||||
@Input() fixedHeight = false;
|
||||
lines: string;
|
||||
private sub;
|
||||
|
||||
public constructor(private service: TruncatableService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.setLines();
|
||||
}
|
||||
|
||||
private setLines() {
|
||||
this.sub = this.service.isCollapsed(this.id).subscribe((collapsed: boolean) => {
|
||||
if (collapsed) {
|
||||
this.lines = this.minLines.toString();
|
||||
} else {
|
||||
this.lines = this.maxLines < 0 ? 'none' : this.maxLines.toString();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
}
|
39
src/app/shared/truncatable/truncatable.actions.ts
Normal file
39
src/app/shared/truncatable/truncatable.actions.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
import { type } from '../ngrx/type';
|
||||
|
||||
/**
|
||||
* For each action type in an action group, make a simple
|
||||
* enum object for all of this group's action types.
|
||||
*
|
||||
* The 'type' utility function coerces strings into string
|
||||
* literal types and runs a simple check to guarantee all
|
||||
* action types in the application are unique.
|
||||
*/
|
||||
export const TruncatableActionTypes = {
|
||||
TOGGLE: type('dspace/truncatable/TOGGLE'),
|
||||
COLLAPSE: type('dspace/truncatable/COLLAPSE'),
|
||||
EXPAND: type('dspace/truncatable/EXPAND'),
|
||||
};
|
||||
|
||||
export class TruncatableAction implements Action {
|
||||
id: string;
|
||||
type;
|
||||
constructor(name: string) {
|
||||
this.id = name;
|
||||
}
|
||||
}
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
export class TruncatableToggleAction extends TruncatableAction {
|
||||
type = TruncatableActionTypes.TOGGLE;
|
||||
}
|
||||
|
||||
export class TruncatableCollapseAction extends TruncatableAction {
|
||||
type = TruncatableActionTypes.COLLAPSE;
|
||||
}
|
||||
|
||||
export class TruncatableExpandAction extends TruncatableAction {
|
||||
type = TruncatableActionTypes.EXPAND;
|
||||
}
|
||||
|
||||
/* tslint:enable:max-classes-per-file */
|
3
src/app/shared/truncatable/truncatable.component.html
Normal file
3
src/app/shared/truncatable/truncatable.component.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<div dsDragClick (actualClick)="toggle()" (mouseenter)="hoverExpand()" (mouseleave)="hoverCollapse">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
101
src/app/shared/truncatable/truncatable.component.spec.ts
Normal file
101
src/app/shared/truncatable/truncatable.component.spec.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { TruncatableComponent } from './truncatable.component';
|
||||
import { TruncatableService } from './truncatable.service';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
describe('TruncatableComponent', () => {
|
||||
let comp: TruncatableComponent;
|
||||
let fixture: ComponentFixture<TruncatableComponent>;
|
||||
const identifier = '1234567890';
|
||||
let truncatableService;
|
||||
const truncatableServiceStub: any = {
|
||||
/* tslint:disable:no-empty */
|
||||
isCollapsed: (id: string) => {
|
||||
if (id === '1') {
|
||||
return Observable.of(true)
|
||||
} else {
|
||||
return Observable.of(false);
|
||||
}
|
||||
},
|
||||
expand: (id: string) => {
|
||||
},
|
||||
collapse: (id: string) => {
|
||||
},
|
||||
toggle: (id: string) => {
|
||||
}
|
||||
/* tslint:enable:no-empty */
|
||||
};
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [NoopAnimationsModule],
|
||||
declarations: [TruncatableComponent],
|
||||
providers: [
|
||||
{ provide: TruncatableService, useValue: truncatableServiceStub },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).overrideComponent(TruncatableComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
}).compileComponents();
|
||||
}));
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TruncatableComponent);
|
||||
comp = fixture.componentInstance; // TruncatableComponent test instance
|
||||
comp.id = identifier;
|
||||
fixture.detectChanges();
|
||||
truncatableService = (comp as any).service;
|
||||
});
|
||||
|
||||
describe('When the item is hoverable', () => {
|
||||
beforeEach(() => {
|
||||
comp.onHover = true;
|
||||
fixture.detectChanges();
|
||||
})
|
||||
;
|
||||
|
||||
it('should call collapse on the TruncatableService', () => {
|
||||
spyOn(truncatableService, 'collapse');
|
||||
comp.hoverCollapse();
|
||||
expect(truncatableService.collapse).toHaveBeenCalledWith(identifier);
|
||||
});
|
||||
|
||||
it('should call expand on the TruncatableService', () => {
|
||||
spyOn(truncatableService, 'expand');
|
||||
comp.hoverExpand();
|
||||
expect(truncatableService.expand).toHaveBeenCalledWith(identifier);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the item is not hoverable', () => {
|
||||
beforeEach(() => {
|
||||
comp.onHover = false;
|
||||
fixture.detectChanges();
|
||||
})
|
||||
;
|
||||
|
||||
it('should not call collapse on the TruncatableService', () => {
|
||||
spyOn(truncatableService, 'collapse');
|
||||
comp.hoverCollapse();
|
||||
expect(truncatableService.collapse).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call expand on the TruncatableService', () => {
|
||||
spyOn(truncatableService, 'expand');
|
||||
comp.hoverExpand();
|
||||
expect(truncatableService.expand).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When toggle is called', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(truncatableService, 'toggle');
|
||||
comp.toggle();
|
||||
});
|
||||
|
||||
it('should call toggle on the TruncatableService', () => {
|
||||
expect(truncatableService.toggle).toHaveBeenCalledWith(identifier);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
44
src/app/shared/truncatable/truncatable.component.ts
Normal file
44
src/app/shared/truncatable/truncatable.component.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
Component, Input
|
||||
} from '@angular/core';
|
||||
import { TruncatableService } from './truncatable.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-truncatable',
|
||||
templateUrl: './truncatable.component.html',
|
||||
styleUrls: ['./truncatable.component.scss'],
|
||||
|
||||
})
|
||||
export class TruncatableComponent {
|
||||
@Input() initialExpand = false;
|
||||
@Input() id: string;
|
||||
@Input() onHover = false;
|
||||
|
||||
public constructor(private service: TruncatableService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.initialExpand) {
|
||||
this.service.expand(this.id);
|
||||
} else {
|
||||
this.service.collapse(this.id);
|
||||
}
|
||||
}
|
||||
|
||||
public hoverCollapse() {
|
||||
if (this.onHover) {
|
||||
this.service.collapse(this.id);
|
||||
}
|
||||
}
|
||||
|
||||
public hoverExpand() {
|
||||
if (this.onHover) {
|
||||
this.service.expand(this.id);
|
||||
}
|
||||
}
|
||||
|
||||
public toggle() {
|
||||
this.service.toggle(this.id);
|
||||
}
|
||||
|
||||
}
|
96
src/app/shared/truncatable/truncatable.reducer.spec.ts
Normal file
96
src/app/shared/truncatable/truncatable.reducer.spec.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import * as deepFreeze from 'deep-freeze';
|
||||
|
||||
import { truncatableReducer } from './truncatable.reducer';
|
||||
import {
|
||||
TruncatableCollapseAction, TruncatableExpandAction,
|
||||
TruncatableToggleAction
|
||||
} from './truncatable.actions';
|
||||
|
||||
const id1 = '123';
|
||||
const id2 = '456';
|
||||
class NullAction extends TruncatableCollapseAction {
|
||||
type = null;
|
||||
constructor() {
|
||||
super(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
describe('truncatableReducer', () => {
|
||||
|
||||
it('should return the current state when no valid actions have been made', () => {
|
||||
const state = { 123: { collapsed: true, page: 1 } };
|
||||
const action = new NullAction();
|
||||
const newState = truncatableReducer(state, action);
|
||||
|
||||
expect(newState).toEqual(state);
|
||||
});
|
||||
|
||||
it('should start with an empty object', () => {
|
||||
const state = Object.create({});
|
||||
const action = new NullAction();
|
||||
const initialState = truncatableReducer(undefined, action);
|
||||
|
||||
// The search filter starts collapsed
|
||||
expect(initialState).toEqual(state);
|
||||
});
|
||||
|
||||
it('should set collapsed to true in response to the COLLAPSE action', () => {
|
||||
const state = {};
|
||||
state[id1] = { collapsed: false};
|
||||
const action = new TruncatableCollapseAction(id1);
|
||||
const newState = truncatableReducer(state, action);
|
||||
|
||||
expect(newState[id1].collapsed).toEqual(true);
|
||||
});
|
||||
|
||||
it('should perform the COLLAPSE action without affecting the previous state', () => {
|
||||
const state = {};
|
||||
state[id1] = { collapsed: false};
|
||||
deepFreeze([state]);
|
||||
|
||||
const action = new TruncatableCollapseAction(id1);
|
||||
truncatableReducer(state, action);
|
||||
|
||||
// no expect required, deepFreeze will ensure an exception is thrown if the state
|
||||
// is mutated, and any uncaught exception will cause the test to fail
|
||||
});
|
||||
|
||||
it('should set filterCollapsed to false in response to the EXPAND action', () => {
|
||||
const state = {};
|
||||
state[id1] = { collapsed: true };
|
||||
const action = new TruncatableExpandAction(id1);
|
||||
const newState = truncatableReducer(state, action);
|
||||
|
||||
expect(newState[id1].collapsed).toEqual(false);
|
||||
});
|
||||
|
||||
it('should perform the EXPAND action without affecting the previous state', () => {
|
||||
const state = {};
|
||||
state[id1] = { collapsed: true };
|
||||
deepFreeze([state]);
|
||||
|
||||
const action = new TruncatableExpandAction(id1);
|
||||
truncatableReducer(state, action);
|
||||
});
|
||||
|
||||
it('should flip the value of filterCollapsed in response to the TOGGLE action', () => {
|
||||
const state1 = {};
|
||||
state1[id1] = { collapsed: true };
|
||||
const action = new TruncatableToggleAction(id1);
|
||||
|
||||
const state2 = truncatableReducer(state1, action);
|
||||
const state3 = truncatableReducer(state2, action);
|
||||
|
||||
expect(state2[id1].collapsed).toEqual(false);
|
||||
expect(state3[id1].collapsed).toEqual(true);
|
||||
});
|
||||
|
||||
it('should perform the TOGGLE action without affecting the previous state', () => {
|
||||
const state = {};
|
||||
state[id2] = { collapsed: true };
|
||||
deepFreeze([state]);
|
||||
|
||||
const action = new TruncatableToggleAction(id2);
|
||||
truncatableReducer(state, action);
|
||||
});
|
||||
});
|
43
src/app/shared/truncatable/truncatable.reducer.ts
Normal file
43
src/app/shared/truncatable/truncatable.reducer.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { TruncatableAction, TruncatableActionTypes } from './truncatable.actions';
|
||||
|
||||
export interface TruncatableState {
|
||||
collapsed: boolean;
|
||||
}
|
||||
|
||||
export interface TruncatablesState {
|
||||
[id: string]: TruncatableState
|
||||
}
|
||||
|
||||
const initialState: TruncatablesState = Object.create(null);
|
||||
|
||||
export function truncatableReducer(state = initialState, action: TruncatableAction): TruncatablesState {
|
||||
|
||||
switch (action.type) {
|
||||
|
||||
case TruncatableActionTypes.COLLAPSE: {
|
||||
return Object.assign({}, state, {
|
||||
[action.id]: {
|
||||
collapsed: true,
|
||||
}
|
||||
});
|
||||
} case TruncatableActionTypes.EXPAND: {
|
||||
return Object.assign({}, state, {
|
||||
[action.id]: {
|
||||
collapsed: false,
|
||||
}
|
||||
});
|
||||
} case TruncatableActionTypes.TOGGLE: {
|
||||
if (!state[action.id]) {
|
||||
state[action.id] = {collapsed: false};
|
||||
}
|
||||
return Object.assign({}, state, {
|
||||
[action.id]: {
|
||||
collapsed: !state[action.id].collapsed,
|
||||
}
|
||||
});
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
54
src/app/shared/truncatable/truncatable.service.spec.ts
Normal file
54
src/app/shared/truncatable/truncatable.service.spec.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Store } from '@ngrx/store';
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { TruncatableService } from './truncatable.service';
|
||||
import { TruncatableCollapseAction, TruncatableExpandAction } from './truncatable.actions';
|
||||
import { TruncatablesState } from './truncatable.reducer';
|
||||
|
||||
describe('TruncatableService', () => {
|
||||
const id1 = '123';
|
||||
const id2 = '456';
|
||||
let service: TruncatableService;
|
||||
const store: Store<TruncatablesState> = jasmine.createSpyObj('store', {
|
||||
/* tslint:disable:no-empty */
|
||||
dispatch: {},
|
||||
/* tslint:enable:no-empty */
|
||||
select: Observable.of(true)
|
||||
});
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
|
||||
providers: [
|
||||
{
|
||||
provide: Store, useValue: store
|
||||
}
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
service = new TruncatableService(store);
|
||||
});
|
||||
|
||||
describe('when the collapse method is triggered', () => {
|
||||
beforeEach(() => {
|
||||
service.collapse(id1);
|
||||
});
|
||||
|
||||
it('TruncatableCollapseAction should be dispatched to the store', () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new TruncatableCollapseAction(id1));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when the expand method is triggered', () => {
|
||||
beforeEach(() => {
|
||||
service.expand(id2);
|
||||
});
|
||||
|
||||
it('TruncatableExpandAction should be dispatched to the store', () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new TruncatableExpandAction(id2));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
52
src/app/shared/truncatable/truncatable.service.ts
Normal file
52
src/app/shared/truncatable/truncatable.service.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { createSelector, MemoizedSelector, Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { TruncatablesState, TruncatableState } from './truncatable.reducer';
|
||||
import { TruncatableExpandAction, TruncatableToggleAction, TruncatableCollapseAction } from './truncatable.actions';
|
||||
import { hasValue } from '../empty.util';
|
||||
|
||||
const truncatableStateSelector = (state: TruncatablesState) => state.truncatable;
|
||||
|
||||
@Injectable()
|
||||
export class TruncatableService {
|
||||
|
||||
constructor(private store: Store<TruncatablesState>) {
|
||||
}
|
||||
|
||||
isCollapsed(id: string): Observable<boolean> {
|
||||
return this.store.select(truncatableByIdSelector(id))
|
||||
.map((object: TruncatableState) => {
|
||||
if (object) {
|
||||
return object.collapsed;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public toggle(id: string): void {
|
||||
this.store.dispatch(new TruncatableToggleAction(id));
|
||||
}
|
||||
|
||||
public collapse(id: string): void {
|
||||
this.store.dispatch(new TruncatableCollapseAction(id));
|
||||
}
|
||||
|
||||
public expand(id: string): void {
|
||||
this.store.dispatch(new TruncatableExpandAction(id));
|
||||
}
|
||||
}
|
||||
|
||||
function truncatableByIdSelector(id: string): MemoizedSelector<TruncatablesState, TruncatableState> {
|
||||
return keySelector<TruncatableState>(id);
|
||||
}
|
||||
|
||||
export function keySelector<T>(key: string): MemoizedSelector<TruncatablesState, T> {
|
||||
return createSelector(truncatableStateSelector, (state: TruncatableState) => {
|
||||
if (hasValue(state)) {
|
||||
return state[key];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
}
|
23
src/app/shared/utils/drag-click.directive.ts
Normal file
23
src/app/shared/utils/drag-click.directive.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Directive, EventEmitter, HostListener, Output } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[dsDragClick]'
|
||||
})
|
||||
export class DragClickDirective {
|
||||
private start;
|
||||
@Output() actualClick = new EventEmitter();
|
||||
|
||||
@HostListener('mousedown', ['$event'])
|
||||
mousedownEvent(event) {
|
||||
this.start = new Date();
|
||||
}
|
||||
|
||||
@HostListener('mouseup', ['$event'])
|
||||
mouseupEvent(event) {
|
||||
const end: any = new Date();
|
||||
const clickTime = end - this.start;
|
||||
if (clickTime < 250) {
|
||||
this.actualClick.emit(event)
|
||||
}
|
||||
}
|
||||
}
|
@@ -29,3 +29,8 @@ $theme-colors: (
|
||||
/* Fonts */
|
||||
$link-color: map-get($theme-colors, info) !default;
|
||||
|
||||
$navbar-dark-color: rgba(white, .5) !default;
|
||||
$navbar-light-color: rgba(black, .5) !default;
|
||||
$navbar-dark-toggler-icon-bg: url("data%3Aimage%2Fsvg+xml%3Bcharset%3Dutf8%2C%3Csvg+viewBox%3D%270+0+30+30%27+xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%3Cpath+stroke%3D%27#{$navbar-dark-color}%27+stroke-width%3D%272%27+stroke-linecap%3D%27round%27+stroke-miterlimit%3D%2710%27+d%3D%27M4+7h22M4+15h22M4+23h22%27%2F%3E%3C%2Fsvg%3E");
|
||||
$navbar-light-toggler-icon-bg: url("data%3Aimage%2Fsvg+xml%3Bcharset%3Dutf8%2C%3Csvg+viewBox%3D%270+0+30+30%27+xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%3Cpath+stroke%3D%27#{$navbar-light-color}%27+stroke-width%3D%272%27+stroke-linecap%3D%27round%27+stroke-miterlimit%3D%2710%27+d%3D%27M4+7h22M4+15h22M4+23h22%27%2F%3E%3C%2Fsvg%3E");
|
||||
|
||||
|
@@ -1,4 +1,12 @@
|
||||
@function calculateRem($size) {
|
||||
$remSize: $size / 16px;
|
||||
@return $remSize;
|
||||
}
|
||||
|
||||
@function strip-unit($number) {
|
||||
@if type-of($number) == 'number' and not unitless($number) {
|
||||
@return $number / ($number * 0 + 1);
|
||||
}
|
||||
|
||||
@return $number;
|
||||
}
|
@@ -1,5 +1,3 @@
|
||||
@import '../../node_modules/bootstrap/scss/functions.scss';
|
||||
@import '../../node_modules/bootstrap/scss/mixins.scss';
|
||||
|
||||
/* Custom mixins go here */
|
||||
|
||||
@import '../../node_modules/bootstrap/scss/variables.scss';
|
Reference in New Issue
Block a user