Skip to content

Commit 86a43fe

Browse files
Merged dspace-cris-2023_02_x into task/dspace-cris-2023_02_x/DSC-1864-alignment-dspace-7.6.2
2 parents 71f153a + 4ad390d commit 86a43fe

8 files changed

Lines changed: 272 additions & 169 deletions

File tree

src/app/lucky-search/search/lucky-search.component.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<div *ngIf="showMultipleSearchSection">
1+
<div *ngIf="showMultipleSearchSection$ | async">
22
<ds-page-with-sidebar [id]="'search-page'">
33
<div class="row">
44
<div class="col-12">
@@ -16,15 +16,15 @@ <h5> {{currentFilter.identifier}}: {{currentFilter.value}} {{'lucky.search.multi
1616
</ds-page-with-sidebar>
1717
</div>
1818

19-
<div *ngIf="showEmptySearchSection">
19+
<div *ngIf="showEmptySearchSection$ | async">
2020
<h5 class="text-center">{{'lucky.search.results.notfound' | translate}} {{currentFilter.identifier}}
2121
: {{currentFilter.value}}. {{'lucky.search.retry.search' | translate}}</h5>
2222
<br/>
2323
<div class="col-md-12 d-flex justify-content-center">
2424
<div class="col-md-8">
2525
<ds-search-form [inPlaceSearch]="false"
26-
[query]="currentFilter.value"
27-
[searchPlaceholder]="'search.search-form.placeholder' | translate"> >
26+
[query]="currentFilter.bitstreamValue ? currentFilter.value + ' OR ' + currentFilter.bitstreamValue : currentFilter.value"
27+
[searchPlaceholder]="'search.search-form.placeholder' | translate">
2828
</ds-search-form>
2929
</div>
3030
</div>
Lines changed: 164 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,41 @@
1-
import { ComponentFixture, TestBed } from '@angular/core/testing';
2-
3-
import { LuckySearchComponent } from './lucky-search.component';
4-
import { LuckySearchService } from '../lucky-search.service';
5-
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
6-
import { Router, UrlTree } from '@angular/router';
7-
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
8-
import { createPaginatedList } from '../../shared/testing/utils.test';
9-
import { Item } from '../../core/shared/item.model';
10-
import { of as observableOf } from 'rxjs';
11-
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
12-
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
13-
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
14-
import { TranslateModule } from '@ngx-translate/core';
15-
import { By } from '@angular/platform-browser';
16-
import { SearchResult } from '../../shared/search/models/search-result.model';
17-
import { DSpaceObject } from '../../core/shared/dspace-object.model';
18-
import { BitstreamDataService, MetadataFilter } from '../../core/data/bitstream-data.service';
19-
import { Bitstream } from '../../core/shared/bitstream.model';
20-
import { RouterMock } from '../../shared/mocks/router.mock';
21-
import { MetadataMap, MetadataValue } from '../../core/shared/metadata.models';
22-
import { FileSizePipe } from '../../shared/utils/file-size-pipe';
23-
24-
describe('SearchComponent', () => {
1+
import {ComponentFixture, TestBed} from '@angular/core/testing';
2+
3+
import {LuckySearchComponent} from './lucky-search.component';
4+
import {LuckySearchService} from '../lucky-search.service';
5+
import {SearchConfigurationService} from '../../core/shared/search/search-configuration.service';
6+
import {Router, UrlTree} from '@angular/router';
7+
import {createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$} from '../../shared/remote-data.utils';
8+
import {createPaginatedList} from '../../shared/testing/utils.test';
9+
import {Item} from '../../core/shared/item.model';
10+
import {of as observableOf} from 'rxjs';
11+
import {PaginatedSearchOptions} from '../../shared/search/models/paginated-search-options.model';
12+
import {PaginationComponentOptions} from '../../shared/pagination/pagination-component-options.model';
13+
import {SortDirection, SortOptions} from '../../core/cache/models/sort-options.model';
14+
import {TranslateModule} from '@ngx-translate/core';
15+
import {By} from '@angular/platform-browser';
16+
import {SearchResult} from '../../shared/search/models/search-result.model';
17+
import {DSpaceObject} from '../../core/shared/dspace-object.model';
18+
import {BitstreamDataService, MetadataFilter} from '../../core/data/bitstream-data.service';
19+
import {Bitstream} from '../../core/shared/bitstream.model';
20+
import {RouterMock} from '../../shared/mocks/router.mock';
21+
import {MetadataMap, MetadataValue} from '../../core/shared/metadata.models';
22+
import {FileSizePipe} from '../../shared/utils/file-size-pipe';
23+
import {HardRedirectService} from '../../core/services/hard-redirect.service';
24+
import {getBitstreamDownloadRoute} from '../../app-routing-paths';
25+
import {PLATFORM_ID} from '@angular/core';
26+
import {NotificationsService} from '../../shared/notifications/notifications.service';
27+
import {NotificationsServiceStub} from '../../shared/testing/notifications-service.stub';
28+
29+
describe('LuckySearchComponent', () => {
2530
let fixture: ComponentFixture<LuckySearchComponent>;
31+
const defaultPagination: PaginatedSearchOptions = Object.assign({
32+
id: 'test-pg-id',
33+
pageSize: 10,
34+
currentPage: 1
35+
});
36+
37+
const hardRedirectService = jasmine.createSpyObj('hardRedirectService', ['redirect']);
38+
2639
const collection1 = Object.assign(new Item(), {
2740
uuid: 'item-uuid-1',
2841
name: 'Test item 1'
@@ -66,6 +79,16 @@ describe('SearchComponent', () => {
6679
'value': 'test'
6780
};
6881
const routerStub = new RouterMock();
82+
83+
const bitstreamMetadata = {
84+
'dc.title': [{value: 'test.pdf'} as MetadataValue],
85+
'dc.description': [{value: 'TestDescription'} as MetadataValue]
86+
} as MetadataMap;
87+
const bitstream = Object.assign(
88+
new Bitstream(),
89+
{_name: 'test.pdf', sizeBytes: 15, uuid: 'fa272dbf-e458-4ad2-868b-b4a27c6eac15', metadata: bitstreamMetadata}
90+
) as Bitstream;
91+
6992
beforeEach(async () => {
7093
await TestBed.configureTestingModule({
7194
declarations: [LuckySearchComponent, FileSizePipe],
@@ -74,7 +97,10 @@ describe('SearchComponent', () => {
7497
{provide: Router, useValue: routerStub},
7598
{provide: SearchConfigurationService, useValue: searchConfigServiceStub},
7699
{provide: LuckySearchService, useValue: searchServiceStub},
77-
{provide: BitstreamDataService, useValue: bitstreamDataService}
100+
{provide: BitstreamDataService, useValue: bitstreamDataService},
101+
{provide: HardRedirectService, useValue: hardRedirectService},
102+
{provide: PLATFORM_ID, useValue: 'browser'},
103+
{provide: NotificationsService, useValue: new NotificationsServiceStub()},
78104
],
79105
})
80106
.compileComponents();
@@ -87,31 +113,77 @@ describe('SearchComponent', () => {
87113
});
88114

89115
describe('should search items', () => {
90-
91116
beforeEach(() => {
117+
fixture = TestBed.createComponent(LuckySearchComponent);
118+
component = fixture.componentInstance;
119+
const firstSearchResult = Object.assign(new SearchResult(), {
120+
indexableObject: Object.assign(new DSpaceObject(), {
121+
id: 'd317835d-7b06-4219-91e2-1191900cb897',
122+
uuid: 'd317835d-7b06-4219-91e2-1191900cb897',
123+
name: 'My first publication',
124+
metadata: {
125+
'dspace.entity.type': [
126+
{value: 'Publication'}
127+
]
128+
}
129+
})
130+
});
131+
132+
const secondSearchResult = Object.assign(new SearchResult(), {
133+
indexableObject: Object.assign(new DSpaceObject(), {
134+
id: 'd317835d-7b06-4219-91e2-26565',
135+
uuid: 'd317835d-7b06-4219-91e2-26565',
136+
name: 'publication',
137+
metadata: {
138+
'dspace.entity.type': [
139+
{value: 'Publication'}
140+
]
141+
}
142+
})
143+
});
92144
spyOn(routerStub, 'parseUrl').and.returnValue(urlTree);
145+
const data = createSuccessfulRemoteDataObject(createPaginatedList([
146+
firstSearchResult, secondSearchResult
147+
]));
148+
149+
component.resultsRD$.next(data);
150+
fixture.detectChanges();
93151
});
94152

95153
it('should create', () => {
96154
expect(component).toBeTruthy();
97155
});
98156

99-
it('should show multiple results', () => {
100-
expect(component.showMultipleSearchSection).toEqual(true);
101-
});
102-
103157
it('should display basic search form results', () => {
104158
expect(fixture.debugElement.query(By.css('ds-search-results')))
105159
.toBeTruthy();
106160
});
107161

162+
it('should call router.navigateByUrl when platform is browser', () => {
163+
component.redirect('test-url');
164+
expect(routerStub.navigateByUrl).toHaveBeenCalledWith('test-url', {replaceUrl: true});
165+
});
166+
});
167+
168+
describe('should search bitstreams', () => {
169+
108170
beforeEach(() => {
109171
fixture = TestBed.createComponent(LuckySearchComponent);
110172
component = fixture.componentInstance;
111-
const firstSearchResult = Object.assign(new SearchResult(), {
173+
174+
const bitstreamSearchTree = new UrlTree();
175+
bitstreamSearchTree.queryParams = {
176+
index: 'testIndex',
177+
value: 'testValue',
178+
bitstreamMetadata: 'testMetadata',
179+
bitstreamValue: 'testMetadataValue'
180+
};
181+
182+
const itemUUID = 'd317835d-7b06-4219-91e2-1191900cb897';
183+
const searchResult = Object.assign(new SearchResult(), {
112184
indexableObject: Object.assign(new DSpaceObject(), {
113-
id: 'd317835d-7b06-4219-91e2-1191900cb897',
114-
uuid: 'd317835d-7b06-4219-91e2-1191900cb897',
185+
id: 'd317835d-7b06-4219-91e2-12222',
186+
uuid: itemUUID,
115187
name: 'My first publication',
116188
metadata: {
117189
'dspace.entity.type': [
@@ -120,47 +192,44 @@ describe('SearchComponent', () => {
120192
}
121193
})
122194
});
195+
const data = createSuccessfulRemoteDataObject(createPaginatedList([searchResult]));
196+
const metadataFilters = [{metadataName: 'dc.title', metadataValue: 'title.pdf'}] as MetadataFilter[];
197+
component.bitstreamFilters$.next(metadataFilters);
198+
bitstreamDataService.findByItem.withArgs(itemUUID, 'ORIGINAL', metadataFilters, {})
199+
.and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList([bitstream])));
200+
201+
component.currentFilter = {identifier: 'test', value: 'test', bitstreamValue: 'test'};
202+
component.searchOptions$ = observableOf(defaultPagination);
203+
204+
spyOn((component as any), 'getLuckySearchResults').and.returnValue(observableOf(data));
205+
spyOn((component as any), 'loadBitstreamsAndRedirectIfNeeded').and.returnValue(observableOf([bitstream]));
206+
spyOn((component as any), 'hasBitstreamFilters').and.returnValue(true);
207+
spyOn(component, 'redirect');
208+
spyOn(routerStub, 'parseUrl').and.returnValue(bitstreamSearchTree);
209+
210+
component.resultsRD$.next(data);
123211

124-
const data = createSuccessfulRemoteDataObject(createPaginatedList([
125-
firstSearchResult
126-
]));
127-
component.resultsRD$.next(data as any);
128212
fixture.detectChanges();
129213
});
130214

131-
it('should call navigate or router', () => {
132-
expect(routerStub.navigateByUrl).toHaveBeenCalled();
215+
it('should redirect to bitstream download route when only one bitstream is found', () => {
216+
expect(component.redirect).toHaveBeenCalledWith(getBitstreamDownloadRoute(bitstream));
133217
});
134218

135-
beforeEach(() => {
136-
fixture = TestBed.createComponent(LuckySearchComponent);
137-
component = fixture.componentInstance;
138-
const data = createSuccessfulRemoteDataObject(createPaginatedList([]));
139-
component.resultsRD$.next(data as any);
140-
fixture.detectChanges();
219+
it('should return bitstream filename', () => {
220+
expect(component.fileName(bitstream)).toEqual('test.pdf');
141221
});
142222

143-
it('should not have results', () => {
144-
expect(component.showEmptySearchSection).toEqual(true);
223+
it('should return bitstream description', () => {
224+
expect(component.getDescription(bitstream)).toEqual('TestDescription');
145225
});
146226

147-
it('should display basic search form', () => {
148-
expect(fixture.debugElement.query(By.css('ds-search-form')))
149-
.toBeTruthy();
227+
it('should return bitstream file size', () => {
228+
expect(component.getSize(bitstream)).toEqual(15);
150229
});
151230
});
152231

153-
describe('should search bitstreams', () => {
154-
155-
const bitstreamMetadata = {
156-
'dc.title': [{ value: 'test.pdf' } as MetadataValue],
157-
'dc.description': [{ value: 'TestDescription' } as MetadataValue]
158-
} as MetadataMap;
159-
const bitstream = Object.assign(
160-
new Bitstream(),
161-
{ _name: 'test.pdf', sizeBytes: 15, uuid: 'fa272dbf-e458-4ad2-868b-b4a27c6eac15', metadata: bitstreamMetadata }
162-
) as Bitstream;
163-
232+
describe('one item is available', () => {
164233
beforeEach(() => {
165234
fixture = TestBed.createComponent(LuckySearchComponent);
166235
component = fixture.componentInstance;
@@ -181,44 +250,61 @@ describe('SearchComponent', () => {
181250
name: 'My first publication',
182251
metadata: {
183252
'dspace.entity.type': [
184-
{ value: 'Publication' }
253+
{value: 'Publication'}
185254
]
186255
}
187256
})
188257
});
189258
const data = createSuccessfulRemoteDataObject(createPaginatedList([firstSearchResult]));
190-
const metadataFilters = [{ metadataName: 'dc.title', metadataValue: 'test.pdf' }] as MetadataFilter[];
259+
const metadataFilters = [{metadataName: 'dc.title', metadataValue: 'test.pdf'}] as MetadataFilter[];
191260
component.bitstreamFilters$.next(metadataFilters);
192261
bitstreamDataService.findByItem.withArgs(itemUUID, 'ORIGINAL', metadataFilters, {})
193262
.and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList([bitstream])));
194263

264+
component.currentFilter = {identifier: 'test', value: 'test', bitstreamValue: 'test'};
265+
component.searchOptions$ = observableOf(defaultPagination);
266+
267+
spyOn((component as any), 'getLuckySearchResults').and.returnValue(observableOf(data));
268+
spyOn((component as any), 'loadBitstreamsAndRedirectIfNeeded').and.returnValue(observableOf([bitstream]));
269+
spyOn((component as any), 'hasBitstreamFilters').and.returnValue(true);
195270
spyOn(component, 'redirect');
196-
spyOn(component.bitstreams$, 'next').and.callThrough();
197271
spyOn(routerStub, 'parseUrl').and.returnValue(bitstreamSearchTree);
198272

199-
component.resultsRD$.next(data as any);
273+
component.resultsRD$.next(data);
200274

201275
fixture.detectChanges();
202276
});
203277

204-
it('should load item bitstreams', () => {
205-
expect(component.bitstreams$.next).toHaveBeenCalledWith([bitstream]);
278+
it('should create', () => {
279+
expect(component).toBeTruthy();
206280
});
207281

208-
it('should redirect to bitstream', () => {
209-
expect(component.redirect).toHaveBeenCalledWith(`/bitstreams/${bitstream.uuid}/download`);
282+
it('should redirect to item page when only one result is found', () => {
283+
expect(component.redirect).toHaveBeenCalled();
210284
});
285+
});
211286

212-
it('should return bitstream filename', () => {
213-
expect(component.fileName(bitstream)).toEqual('test.pdf');
214-
});
287+
it('should not redirect when no bitstreams are found', () => {
288+
const item = Object.assign(new Item(), {uuid: 'item-uuid-1', name: 'Test item 1'});
289+
const data = createSuccessfulRemoteDataObject(createPaginatedList([
290+
{indexableObject: item, hitHighlights: {}}
291+
])) as any;
292+
component.resultsRD$.next(data);
293+
component.bitstreamFilters$.next([{metadataName: 'dc.title', metadataValue: 'Non-existent bitstream'}]);
294+
bitstreamDataService.findByItem.and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList([])));
295+
spyOn(component, 'redirect');
296+
fixture.detectChanges();
297+
expect(component.redirect).not.toHaveBeenCalled();
298+
});
215299

216-
it('should return bitstream description', () => {
217-
expect(component.getDescription(bitstream)).toEqual('TestDescription');
218-
});
300+
it('should update showEmptySearchSection$ when no results are found', () => {
301+
const emptyResults = createSuccessfulRemoteDataObject(createPaginatedList([]));
219302

220-
it('should return bitstream file size', () => {
221-
expect(component.getSize(bitstream)).toEqual(15);
222-
});
303+
spyOn(component as any, 'getLuckySearchResults').and.returnValue(observableOf(emptyResults));
304+
spyOn(component as any, 'processSearchResults').and.returnValue(observableOf(emptyResults));
305+
306+
component.getSearchResults();
307+
308+
expect(component.showEmptySearchSection$.getValue()).toBe(true);
223309
});
224310
});

0 commit comments

Comments
 (0)