Skip to content

Commit 1697d0f

Browse files
[DSC-1875] Port of [CST-16286] lucky-search - fix of redirect on SSR & refactor [2424e47308a4a902b3ed33a83522b3f77908bd98]
1 parent a063cbe commit 1697d0f

3 files changed

Lines changed: 250 additions & 161 deletions

File tree

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

Lines changed: 3 additions & 3 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"
2626
[query]="currentFilter.value"
27-
[searchPlaceholder]="'search.search-form.placeholder' | translate"> >
27+
[searchPlaceholder]="'search.search-form.placeholder' | translate">
2828
</ds-search-form>
2929
</div>
3030
</div>
Lines changed: 178 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,39 @@
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+
27+
describe('LuckySearchComponent', () => {
2528
let fixture: ComponentFixture<LuckySearchComponent>;
29+
const defaultPagination: PaginatedSearchOptions = Object.assign({
30+
id: 'test-pg-id',
31+
pageSize: 10,
32+
currentPage: 1
33+
});
34+
35+
const hardRedirectService = jasmine.createSpyObj('hardRedirectService', ['redirect']);
36+
2637
const collection1 = Object.assign(new Item(), {
2738
uuid: 'item-uuid-1',
2839
name: 'Test item 1'
@@ -66,6 +77,16 @@ describe('SearchComponent', () => {
6677
'value': 'test'
6778
};
6879
const routerStub = new RouterMock();
80+
81+
const bitstreamMetadata = {
82+
'dc.title': [{value: 'test.pdf'} as MetadataValue],
83+
'dc.description': [{value: 'TestDescription'} as MetadataValue]
84+
} as MetadataMap;
85+
const bitstream = Object.assign(
86+
new Bitstream(),
87+
{_name: 'test.pdf', sizeBytes: 15, uuid: 'fa272dbf-e458-4ad2-868b-b4a27c6eac15', metadata: bitstreamMetadata}
88+
) as Bitstream;
89+
6990
beforeEach(async () => {
7091
await TestBed.configureTestingModule({
7192
declarations: [LuckySearchComponent, FileSizePipe],
@@ -74,7 +95,9 @@ describe('SearchComponent', () => {
7495
{provide: Router, useValue: routerStub},
7596
{provide: SearchConfigurationService, useValue: searchConfigServiceStub},
7697
{provide: LuckySearchService, useValue: searchServiceStub},
77-
{provide: BitstreamDataService, useValue: bitstreamDataService}
98+
{provide: BitstreamDataService, useValue: bitstreamDataService},
99+
{provide: HardRedirectService, useValue: hardRedirectService},
100+
{provide: PLATFORM_ID, useValue: 'browser'},
78101
],
79102
})
80103
.compileComponents();
@@ -87,24 +110,6 @@ describe('SearchComponent', () => {
87110
});
88111

89112
describe('should search items', () => {
90-
91-
beforeEach(() => {
92-
spyOn(routerStub, 'parseUrl').and.returnValue(urlTree);
93-
});
94-
95-
it('should create', () => {
96-
expect(component).toBeTruthy();
97-
});
98-
99-
it('should show multiple results', () => {
100-
expect(component.showMultipleSearchSection).toEqual(true);
101-
});
102-
103-
it('should display basic search form results', () => {
104-
expect(fixture.debugElement.query(By.css('ds-search-results')))
105-
.toBeTruthy();
106-
});
107-
108113
beforeEach(() => {
109114
fixture = TestBed.createComponent(LuckySearchComponent);
110115
component = fixture.componentInstance;
@@ -121,46 +126,44 @@ describe('SearchComponent', () => {
121126
})
122127
});
123128

129+
const secondSearchResult = Object.assign(new SearchResult(), {
130+
indexableObject: Object.assign(new DSpaceObject(), {
131+
id: 'd317835d-7b06-4219-91e2-26565',
132+
uuid: 'd317835d-7b06-4219-91e2-26565',
133+
name: 'publication',
134+
metadata: {
135+
'dspace.entity.type': [
136+
{value: 'Publication'}
137+
]
138+
}
139+
})
140+
});
141+
spyOn(routerStub, 'parseUrl').and.returnValue(urlTree);
124142
const data = createSuccessfulRemoteDataObject(createPaginatedList([
125-
firstSearchResult
143+
firstSearchResult, secondSearchResult
126144
]));
127-
component.resultsRD$.next(data as any);
128-
fixture.detectChanges();
129-
});
130145

131-
it('should call navigate or router', () => {
132-
expect(routerStub.navigateByUrl).toHaveBeenCalled();
133-
});
134-
135-
beforeEach(() => {
136-
fixture = TestBed.createComponent(LuckySearchComponent);
137-
component = fixture.componentInstance;
138-
const data = createSuccessfulRemoteDataObject(createPaginatedList([]));
139-
component.resultsRD$.next(data as any);
146+
component.resultsRD$.next(data);
140147
fixture.detectChanges();
141148
});
142149

143-
it('should not have results', () => {
144-
expect(component.showEmptySearchSection).toEqual(true);
150+
it('should create', () => {
151+
expect(component).toBeTruthy();
145152
});
146153

147-
it('should display basic search form', () => {
148-
expect(fixture.debugElement.query(By.css('ds-search-form')))
154+
it('should display basic search form results', () => {
155+
expect(fixture.debugElement.query(By.css('ds-search-results')))
149156
.toBeTruthy();
150157
});
158+
159+
it('should call router.navigateByUrl when platform is browser', () => {
160+
component.redirect('test-url');
161+
expect(routerStub.navigateByUrl).toHaveBeenCalledWith('test-url', {replaceUrl: true});
162+
});
151163
});
152164

153165
describe('should search bitstreams', () => {
154166

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-
164167
beforeEach(() => {
165168
fixture = TestBed.createComponent(LuckySearchComponent);
166169
component = fixture.componentInstance;
@@ -174,39 +177,40 @@ describe('SearchComponent', () => {
174177
};
175178

176179
const itemUUID = 'd317835d-7b06-4219-91e2-1191900cb897';
177-
const firstSearchResult = Object.assign(new SearchResult(), {
180+
const searchResult = Object.assign(new SearchResult(), {
178181
indexableObject: Object.assign(new DSpaceObject(), {
179-
id: 'd317835d-7b06-4219-91e2-1191900cb897',
182+
id: 'd317835d-7b06-4219-91e2-12222',
180183
uuid: itemUUID,
181184
name: 'My first publication',
182185
metadata: {
183186
'dspace.entity.type': [
184-
{ value: 'Publication' }
187+
{value: 'Publication'}
185188
]
186189
}
187190
})
188191
});
189-
const data = createSuccessfulRemoteDataObject(createPaginatedList([firstSearchResult]));
190-
const metadataFilters = [{ metadataName: 'dc.title', metadataValue: 'test.pdf' }] as MetadataFilter[];
192+
const data = createSuccessfulRemoteDataObject(createPaginatedList([searchResult]));
193+
const metadataFilters = [{metadataName: 'dc.title', metadataValue: 'title.pdf'}] as MetadataFilter[];
191194
component.bitstreamFilters$.next(metadataFilters);
192195
bitstreamDataService.findByItem.withArgs(itemUUID, 'ORIGINAL', metadataFilters, {})
193196
.and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList([bitstream])));
194197

198+
component.currentFilter = {identifier: 'test', value: 'test'};
199+
component.searchOptions$ = observableOf(defaultPagination);
200+
201+
spyOn((component as any), 'getLuckySearchResults').and.returnValue(observableOf(data));
202+
spyOn((component as any), 'loadBitstreamsAndRedirectIfNeeded').and.returnValue(observableOf([bitstream]));
203+
spyOn((component as any), 'hasBitstreamFilters').and.returnValue(true);
195204
spyOn(component, 'redirect');
196-
spyOn(component.bitstreams$, 'next').and.callThrough();
197205
spyOn(routerStub, 'parseUrl').and.returnValue(bitstreamSearchTree);
198206

199-
component.resultsRD$.next(data as any);
207+
component.resultsRD$.next(data);
200208

201209
fixture.detectChanges();
202210
});
203211

204-
it('should load item bitstreams', () => {
205-
expect(component.bitstreams$.next).toHaveBeenCalledWith([bitstream]);
206-
});
207-
208-
it('should redirect to bitstream', () => {
209-
expect(component.redirect).toHaveBeenCalledWith(`/bitstreams/${bitstream.uuid}/download`);
212+
it('should redirect to bitstream download route when only one bitstream is found', () => {
213+
expect(component.redirect).toHaveBeenCalledWith(getBitstreamDownloadRoute(bitstream));
210214
});
211215

212216
it('should return bitstream filename', () => {
@@ -221,4 +225,96 @@ describe('SearchComponent', () => {
221225
expect(component.getSize(bitstream)).toEqual(15);
222226
});
223227
});
228+
229+
describe('one item is available', () => {
230+
beforeEach(() => {
231+
fixture = TestBed.createComponent(LuckySearchComponent);
232+
component = fixture.componentInstance;
233+
234+
const bitstreamSearchTree = new UrlTree();
235+
bitstreamSearchTree.queryParams = {
236+
index: 'testIndex',
237+
value: 'testValue',
238+
bitstreamMetadata: 'testMetadata',
239+
bitstreamValue: 'testMetadataValue'
240+
};
241+
242+
const itemUUID = 'd317835d-7b06-4219-91e2-1191900cb897';
243+
const firstSearchResult = Object.assign(new SearchResult(), {
244+
indexableObject: Object.assign(new DSpaceObject(), {
245+
id: 'd317835d-7b06-4219-91e2-1191900cb897',
246+
uuid: itemUUID,
247+
name: 'My first publication',
248+
metadata: {
249+
'dspace.entity.type': [
250+
{value: 'Publication'}
251+
]
252+
}
253+
})
254+
});
255+
const data = createSuccessfulRemoteDataObject(createPaginatedList([firstSearchResult]));
256+
const metadataFilters = [{metadataName: 'dc.title', metadataValue: 'test.pdf'}] as MetadataFilter[];
257+
component.bitstreamFilters$.next(metadataFilters);
258+
bitstreamDataService.findByItem.withArgs(itemUUID, 'ORIGINAL', metadataFilters, {})
259+
.and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList([bitstream])));
260+
261+
component.currentFilter = {identifier: 'test', value: 'test'};
262+
component.searchOptions$ = observableOf(defaultPagination);
263+
264+
spyOn((component as any), 'getLuckySearchResults').and.returnValue(observableOf(data));
265+
spyOn((component as any), 'loadBitstreamsAndRedirectIfNeeded').and.returnValue(observableOf([bitstream]));
266+
spyOn((component as any), 'hasBitstreamFilters').and.returnValue(true);
267+
spyOn(component, 'redirect');
268+
spyOn(routerStub, 'parseUrl').and.returnValue(bitstreamSearchTree);
269+
270+
component.resultsRD$.next(data);
271+
272+
fixture.detectChanges();
273+
});
274+
275+
it('should create', () => {
276+
expect(component).toBeTruthy();
277+
});
278+
279+
it('should redirect to item page when only one result is found', () => {
280+
expect(component.redirect).toHaveBeenCalled();
281+
});
282+
});
283+
284+
it('should not redirect when no bitstreams are found', () => {
285+
const item = Object.assign(new Item(), {uuid: 'item-uuid-1', name: 'Test item 1'});
286+
const data = createSuccessfulRemoteDataObject(createPaginatedList([
287+
{indexableObject: item, hitHighlights: {}}
288+
])) as any;
289+
component.resultsRD$.next(data);
290+
component.bitstreamFilters$.next([{metadataName: 'dc.title', metadataValue: 'Non-existent bitstream'}]);
291+
bitstreamDataService.findByItem.and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList([])));
292+
spyOn(component, 'redirect');
293+
fixture.detectChanges();
294+
expect(component.redirect).not.toHaveBeenCalled();
295+
});
296+
297+
it('should update showEmptySearchSection$ and showMultipleSearchSection$ based on search results', () => {
298+
const emptyResults = createSuccessfulRemoteDataObject(createPaginatedList([]));
299+
const multipleResults = createSuccessfulRemoteDataObject(createPaginatedList([
300+
new SearchResult<DSpaceObject>(),
301+
new SearchResult<DSpaceObject>()
302+
]));
303+
304+
spyOn(component as any, 'getLuckySearchResults').and.returnValue(observableOf(emptyResults));
305+
spyOn(component as any, 'processSearchResults').and.returnValue(observableOf(emptyResults));
306+
307+
component.getSearchResults();
308+
309+
expect(component.showEmptySearchSection$.getValue()).toBe(true);
310+
expect(component.showMultipleSearchSection$.getValue()).toBe(false);
311+
312+
(component as any).getLuckySearchResults.and.returnValue(observableOf(multipleResults));
313+
(component as any).processSearchResults.and.returnValue(observableOf(multipleResults));
314+
315+
component.getSearchResults();
316+
317+
expect(component.showEmptySearchSection$.getValue()).toBe(false);
318+
expect(component.showMultipleSearchSection$.getValue()).toBe(true);
319+
});
224320
});

0 commit comments

Comments
 (0)