improved coverage, type docs, removed startsWith option and general refactoring

This commit is contained in:
Kristof De Langhe
2018-08-09 12:37:09 +02:00
parent 883a1d8881
commit 072507b293
13 changed files with 74 additions and 90 deletions

View File

@@ -18,6 +18,9 @@ import { Item } from '../../core/shared/item.model';
styleUrls: ['./browse-by-author-page.component.scss'], styleUrls: ['./browse-by-author-page.component.scss'],
templateUrl: './browse-by-author-page.component.html' templateUrl: './browse-by-author-page.component.html'
}) })
/**
* Component for browsing (items) by author (dc.contributor.author)
*/
export class BrowseByAuthorPageComponent implements OnInit { export class BrowseByAuthorPageComponent implements OnInit {
authors$: Observable<RemoteData<PaginatedList<BrowseEntry>>>; authors$: Observable<RemoteData<PaginatedList<BrowseEntry>>>;
@@ -55,7 +58,6 @@ export class BrowseByAuthorPageComponent implements OnInit {
const pageSize = +params.pageSize || this.paginationConfig.pageSize; const pageSize = +params.pageSize || this.paginationConfig.pageSize;
const sortDirection = params.sortDirection || this.sortConfig.direction; const sortDirection = params.sortDirection || this.sortConfig.direction;
const sortField = params.sortField || this.sortConfig.field; const sortField = params.sortField || this.sortConfig.field;
const startsWith = +params.query || params.query || '';
this.value = +params.value || params.value || ''; this.value = +params.value || params.value || '';
const pagination = Object.assign({}, const pagination = Object.assign({},
this.paginationConfig, this.paginationConfig,
@@ -67,8 +69,7 @@ export class BrowseByAuthorPageComponent implements OnInit {
); );
const searchOptions = { const searchOptions = {
pagination: pagination, pagination: pagination,
sort: sort, sort: sort
startsWith: startsWith
}; };
if (isNotEmpty(this.value)) { if (isNotEmpty(this.value)) {
this.updatePageWithItems(searchOptions, this.value); this.updatePageWithItems(searchOptions, this.value);
@@ -79,10 +80,10 @@ export class BrowseByAuthorPageComponent implements OnInit {
} }
/** /**
* Updates the current page with searchOptions
* @param searchOptions Options to narrow down your search: * @param searchOptions Options to narrow down your search:
* { pagination: PaginationComponentOptions, * { pagination: PaginationComponentOptions,
* sort: SortOptions, * sort: SortOptions }
* startsWith: string }
*/ */
updatePage(searchOptions) { updatePage(searchOptions) {
this.authors$ = this.browseService.getBrowseEntriesFor('author', searchOptions); this.authors$ = this.browseService.getBrowseEntriesFor('author', searchOptions);
@@ -90,10 +91,10 @@ export class BrowseByAuthorPageComponent implements OnInit {
} }
/** /**
* Updates the current page with searchOptions and display items linked to author
* @param searchOptions Options to narrow down your search: * @param searchOptions Options to narrow down your search:
* { pagination: PaginationComponentOptions, * { pagination: PaginationComponentOptions,
* sort: SortOptions, * sort: SortOptions }
* startsWith: string }
* @param author The author's name for displaying items * @param author The author's name for displaying items
*/ */
updatePageWithItems(searchOptions, author: string) { updatePageWithItems(searchOptions, author: string) {

View File

@@ -17,6 +17,9 @@ import { Collection } from '../../core/shared/collection.model';
styleUrls: ['./browse-by-title-page.component.scss'], styleUrls: ['./browse-by-title-page.component.scss'],
templateUrl: './browse-by-title-page.component.html' templateUrl: './browse-by-title-page.component.html'
}) })
/**
* Component for browsing items by title (dc.title)
*/
export class BrowseByTitlePageComponent implements OnInit { export class BrowseByTitlePageComponent implements OnInit {
items$: Observable<RemoteData<PaginatedList<Item>>>; items$: Observable<RemoteData<PaginatedList<Item>>>;
@@ -52,7 +55,6 @@ export class BrowseByTitlePageComponent implements OnInit {
const page = +params.page || this.paginationConfig.currentPage; const page = +params.page || this.paginationConfig.currentPage;
const pageSize = +params.pageSize || this.paginationConfig.pageSize; const pageSize = +params.pageSize || this.paginationConfig.pageSize;
const sortDirection = +params.page || this.sortConfig.direction; const sortDirection = +params.page || this.sortConfig.direction;
const startsWith = +params.query || params.query || '';
const pagination = Object.assign({}, const pagination = Object.assign({},
this.paginationConfig, this.paginationConfig,
{ currentPage: page, pageSize: pageSize } { currentPage: page, pageSize: pageSize }
@@ -63,18 +65,22 @@ export class BrowseByTitlePageComponent implements OnInit {
); );
this.updatePage({ this.updatePage({
pagination: pagination, pagination: pagination,
sort: sort, sort: sort
startsWith: startsWith
}); });
})); }));
} }
/**
* Updates the current page with searchOptions
* @param searchOptions Options to narrow down your search:
* { pagination: PaginationComponentOptions,
* sort: SortOptions }
*/
updatePage(searchOptions) { updatePage(searchOptions) {
this.items$ = this.itemDataService.findAll({ this.items$ = this.itemDataService.findAll({
currentPage: searchOptions.pagination.currentPage, currentPage: searchOptions.pagination.currentPage,
elementsPerPage: searchOptions.pagination.pageSize, elementsPerPage: searchOptions.pagination.pageSize,
sort: searchOptions.sort, sort: searchOptions.sort
startsWith: searchOptions.startsWith
}); });
} }

View File

@@ -6,7 +6,7 @@ import { getMockResponseCacheService } from '../../shared/mocks/mock-response-ca
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub'; import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ResponseCacheService } from '../cache/response-cache.service'; import { ResponseCacheService } from '../cache/response-cache.service';
import { BrowseEndpointRequest, BrowseEntriesRequest } from '../data/request.models'; import { BrowseEndpointRequest, BrowseEntriesRequest, BrowseItemsRequest } from '../data/request.models';
import { RequestService } from '../data/request.service'; import { RequestService } from '../data/request.service';
import { BrowseDefinition } from '../shared/browse-definition.model'; import { BrowseDefinition } from '../shared/browse-definition.model';
import { BrowseService } from './browse.service'; import { BrowseService } from './browse.service';
@@ -143,7 +143,9 @@ describe('BrowseService', () => {
}); });
describe('getBrowseEntriesFor', () => { describe('getBrowseEntriesFor and getBrowseItemsFor', () => {
const mockAuthorName = 'Donald Smith';
beforeEach(() => { beforeEach(() => {
responseCache = initMockResponseCacheService(true); responseCache = initMockResponseCacheService(true);
requestService = getMockRequestService(); requestService = getMockRequestService();
@@ -156,7 +158,7 @@ describe('BrowseService', () => {
spyOn(rdbService, 'toRemoteDataObservable').and.callThrough(); spyOn(rdbService, 'toRemoteDataObservable').and.callThrough();
}); });
describe('when called with a valid browse definition id', () => { describe('when getBrowseEntriesFor is called with a valid browse definition id', () => {
it('should configure a new BrowseEntriesRequest', () => { it('should configure a new BrowseEntriesRequest', () => {
const expected = new BrowseEntriesRequest(requestService.generateRequestId(), browseDefinitions[1]._links.entries); const expected = new BrowseEntriesRequest(requestService.generateRequestId(), browseDefinitions[1]._links.entries);
@@ -175,7 +177,26 @@ describe('BrowseService', () => {
}); });
describe('when called with an invalid browse definition id', () => { describe('when getBrowseItemsFor is called with a valid browse definition id', () => {
it('should configure a new BrowseItemsRequest', () => {
const expected = new BrowseItemsRequest(requestService.generateRequestId(), browseDefinitions[1]._links.items + '?filterValue=' + mockAuthorName);
scheduler.schedule(() => service.getBrowseItemsFor(browseDefinitions[1].id, mockAuthorName).subscribe());
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(expected);
});
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
service.getBrowseItemsFor(browseDefinitions[1].id, mockAuthorName);
expect(rdbService.toRemoteDataObservable).toHaveBeenCalled();
});
});
describe('when getBrowseEntriesFor is called with an invalid browse definition id', () => {
it('should throw an Error', () => { it('should throw an Error', () => {
const definitionID = 'invalidID'; const definitionID = 'invalidID';
@@ -184,6 +205,16 @@ describe('BrowseService', () => {
expect(service.getBrowseEntriesFor(definitionID)).toBeObservable(expected); expect(service.getBrowseEntriesFor(definitionID)).toBeObservable(expected);
}); });
}); });
describe('when getBrowseItemsFor is called with an invalid browse definition id', () => {
it('should throw an Error', () => {
const definitionID = 'invalidID';
const expected = cold('--#-', undefined, new Error(`No metadata browse definition could be found for id '${definitionID}'`))
expect(service.getBrowseItemsFor(definitionID, mockAuthorName)).toBeObservable(expected);
});
});
}); });
describe('getBrowseURLFor', () => { describe('getBrowseURLFor', () => {

View File

@@ -133,6 +133,15 @@ export class BrowseService {
return this.rdb.toRemoteDataObservable(requestEntry$, responseCache$, payload$); return this.rdb.toRemoteDataObservable(requestEntry$, responseCache$, payload$);
} }
/**
* Get all items linked to a certain metadata value
* @param {string} definitionID definition ID to define the metadata-field (e.g. author)
* @param {string} filterValue metadata value to filter by (e.g. author's name)
* @param options Options to narrow down your search:
* { pagination: PaginationComponentOptions,
* sort: SortOptions }
* @returns {Observable<RemoteData<PaginatedList<Item>>>}
*/
getBrowseItemsFor(definitionID: string, filterValue: string, options: { getBrowseItemsFor(definitionID: string, filterValue: string, options: {
pagination?: PaginationComponentOptions; pagination?: PaginationComponentOptions;
sort?: SortOptions; sort?: SortOptions;

View File

@@ -21,19 +21,17 @@ describe('ItemDataService', () => {
const halEndpointService = {} as HALEndpointService; const halEndpointService = {} as HALEndpointService;
const scopeID = '4af28e99-6a9c-4036-a199-e1b587046d39'; const scopeID = '4af28e99-6a9c-4036-a199-e1b587046d39';
const startsWith = 'a';
const options = Object.assign(new FindAllOptions(), { const options = Object.assign(new FindAllOptions(), {
scopeID: scopeID, scopeID: scopeID,
sort: { sort: {
field: '', field: '',
direction: undefined direction: undefined
}, }
startsWith: startsWith
}); });
const browsesEndpoint = 'https://rest.api/discover/browses'; const browsesEndpoint = 'https://rest.api/discover/browses';
const itemBrowseEndpoint = `${browsesEndpoint}/author/items`; const itemBrowseEndpoint = `${browsesEndpoint}/author/items`;
const scopedEndpoint = `${itemBrowseEndpoint}?scope=${scopeID}&startsWith=${startsWith}`; const scopedEndpoint = `${itemBrowseEndpoint}?scope=${scopeID}`;
const serviceEndpoint = `https://rest.api/core/items`; const serviceEndpoint = `https://rest.api/core/items`;
const browseError = new Error('getBrowseURL failed'); const browseError = new Error('getBrowseURL failed');

View File

@@ -38,7 +38,7 @@ export class ItemDataService extends DataService<NormalizedItem, Item> {
} }
return this.bs.getBrowseURLFor(field, this.linkPath) return this.bs.getBrowseURLFor(field, this.linkPath)
.filter((href: string) => isNotEmpty(href)) .filter((href: string) => isNotEmpty(href))
.map((href: string) => new URLCombiner(href, `?scope=${options.scopeID}` + (options.startsWith ? `&startsWith=${options.startsWith}` : '')).toString()) .map((href: string) => new URLCombiner(href, `?scope=${options.scopeID}`).toString())
.distinctUntilChanged(); .distinctUntilChanged();
} }

View File

@@ -47,6 +47,11 @@ export const getRemoteDataPayload = () =>
<T>(source: Observable<RemoteData<T>>): Observable<T> => <T>(source: Observable<RemoteData<T>>): Observable<T> =>
source.pipe(map((remoteData: RemoteData<T>) => remoteData.payload)); source.pipe(map((remoteData: RemoteData<T>) => remoteData.payload));
/**
* Get the browse links from a definition by ID given an array of all definitions
* @param {string} definitionID
* @returns {(source: Observable<RemoteData<BrowseDefinition[]>>) => Observable<any>}
*/
export const getBrowseDefinitionLinks = (definitionID: string) => export const getBrowseDefinitionLinks = (definitionID: string) =>
(source: Observable<RemoteData<BrowseDefinition[]>>): Observable<any> => (source: Observable<RemoteData<BrowseDefinition[]>>): Observable<any> =>
source.pipe( source.pipe(

View File

@@ -17,6 +17,9 @@ import { ListableObject } from '../object-collection/shared/listable-object.mode
fadeInOut fadeInOut
] ]
}) })
/**
* Component to display a browse-by page for any ListableObject
*/
export class BrowseByComponent { export class BrowseByComponent {
@Input() title: string; @Input() title: string;
@Input() objects$: Observable<RemoteData<PaginatedList<ListableObject>>>; @Input() objects$: Observable<RemoteData<PaginatedList<ListableObject>>>;

View File

@@ -1,3 +0,0 @@
<a [routerLink]="['/browse/' + object.key + '/' + object.value]" class="lead">
{{object.value}}
</a>

View File

@@ -1 +0,0 @@
@import '../../../../styles/variables';

View File

@@ -1,48 +0,0 @@
import { MetadataListElementComponent } from './metadata-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';
import { Metadatum } from '../../../core/shared/metadatum.model';
let metadataListElementComponent: MetadataListElementComponent;
let fixture: ComponentFixture<MetadataListElementComponent>;
const mockValue: Metadatum = Object.assign(new Metadatum(), {
key: 'dc.contributor.author',
value: 'De Langhe Kristof'
});
describe('MetadataListElementComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MetadataListElementComponent , TruncatePipe],
providers: [
{ provide: 'objectElementProvider', useValue: {mockValue}}
],
schemas: [ NO_ERRORS_SCHEMA ]
}).overrideComponent(MetadataListElementComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(MetadataListElementComponent);
metadataListElementComponent = fixture.componentInstance;
}));
describe('When the metadatum is loaded', () => {
beforeEach(() => {
metadataListElementComponent.object = mockValue;
fixture.detectChanges();
});
it('should show the value as a link', () => {
const metadatumLink = fixture.debugElement.query(By.css('a.lead'));
expect(metadatumLink.nativeElement.textContent.trim()).toBe(mockValue.value);
});
});
});

View File

@@ -1,15 +0,0 @@
import { Component, Input, Inject } from '@angular/core';
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
import { renderElementsFor } from '../../object-collection/shared/dso-element-decorator';
import { ViewMode } from '../../../+search-page/search-options.model';
import { Metadatum } from '../../../core/shared/metadatum.model';
@Component({
selector: 'ds-metadata-list-element',
styleUrls: ['./metadata-list-element.component.scss'],
templateUrl: './metadata-list-element.component.html'
})
@renderElementsFor(Metadatum, ViewMode.List)
export class MetadataListElementComponent extends AbstractListableElementComponent<Metadatum> {}

View File

@@ -72,7 +72,6 @@ import { NumberPickerComponent } from './number-picker/number-picker.component';
import { DsDatePickerComponent } from './form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component'; import { DsDatePickerComponent } from './form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component';
import { DsDynamicLookupComponent } from './form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component'; import { DsDynamicLookupComponent } from './form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component';
import { MockAdminGuard } from './mocks/mock-admin-guard.service'; import { MockAdminGuard } from './mocks/mock-admin-guard.service';
import { MetadataListElementComponent } from './object-list/metadata-list-element/metadata-list-element.component';
import { BrowseByModule } from '../+browse-by/browse-by.module'; import { BrowseByModule } from '../+browse-by/browse-by.module';
import { BrowseByComponent } from './browse-by/browse-by.component'; import { BrowseByComponent } from './browse-by/browse-by.component';
import { BrowseEntryListElementComponent } from './object-list/browse-entry-list-element/browse-entry-list-element.component'; import { BrowseEntryListElementComponent } from './object-list/browse-entry-list-element/browse-entry-list-element.component';
@@ -149,7 +148,6 @@ const COMPONENTS = [
const ENTRY_COMPONENTS = [ const ENTRY_COMPONENTS = [
// put shared entry components (components that are created dynamically) here // put shared entry components (components that are created dynamically) here
ItemListElementComponent, ItemListElementComponent,
MetadataListElementComponent,
CollectionListElementComponent, CollectionListElementComponent,
CommunityListElementComponent, CommunityListElementComponent,
SearchResultListElementComponent, SearchResultListElementComponent,