@internetarchive/collection-browser 0.3.7-alpha.1 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/app-root.d.ts +1 -0
- package/dist/src/app-root.js +29 -8
- package/dist/src/app-root.js.map +1 -1
- package/dist/src/collection-browser.d.ts +33 -1
- package/dist/src/collection-browser.js +118 -44
- package/dist/src/collection-browser.js.map +1 -1
- package/dist/src/collection-facets/more-facets-content.js +3 -3
- package/dist/src/collection-facets/more-facets-content.js.map +1 -1
- package/dist/src/collection-facets.js +1 -1
- package/dist/src/collection-facets.js.map +1 -1
- package/dist/src/tiles/list/tile-list.js +6 -3
- package/dist/src/tiles/list/tile-list.js.map +1 -1
- package/dist/test/collection-browser.test.js +139 -0
- package/dist/test/collection-browser.test.js.map +1 -1
- package/dist/test/mocks/mock-search-responses.d.ts +2 -0
- package/dist/test/mocks/mock-search-responses.js +74 -1
- package/dist/test/mocks/mock-search-responses.js.map +1 -1
- package/dist/test/mocks/mock-search-service.js +5 -1
- package/dist/test/mocks/mock-search-service.js.map +1 -1
- package/dist/test/tiles/list/tile-list.test.js +29 -0
- package/dist/test/tiles/list/tile-list.test.js.map +1 -1
- package/package.json +3 -3
- package/src/app-root.ts +33 -10
- package/src/collection-browser.ts +139 -44
- package/src/collection-facets/more-facets-content.ts +3 -3
- package/src/collection-facets.ts +1 -1
- package/src/tiles/list/tile-list.ts +6 -2
- package/test/collection-browser.test.ts +207 -0
- package/test/mocks/mock-search-responses.ts +82 -0
- package/test/mocks/mock-search-service.ts +6 -0
- package/test/tiles/list/tile-list.test.ts +40 -0
|
@@ -4,6 +4,7 @@ import { html } from 'lit';
|
|
|
4
4
|
import sinon from 'sinon';
|
|
5
5
|
import type { InfiniteScroller } from '@internetarchive/infinite-scroller';
|
|
6
6
|
import { SearchType } from '@internetarchive/search-service';
|
|
7
|
+
import type { HistogramDateRange } from '@internetarchive/histogram-date-range';
|
|
7
8
|
import type { CollectionBrowser } from '../src/collection-browser';
|
|
8
9
|
import '../src/collection-browser';
|
|
9
10
|
import {
|
|
@@ -17,6 +18,7 @@ import { MockCollectionNameCache } from './mocks/mock-collection-name-cache';
|
|
|
17
18
|
import { MockAnalyticsHandler } from './mocks/mock-analytics-handler';
|
|
18
19
|
import { analyticsCategories } from '../src/utils/analytics-events';
|
|
19
20
|
import type { TileDispatcher } from '../src/tiles/tile-dispatcher';
|
|
21
|
+
import type { CollectionFacets } from '../src/collection-facets';
|
|
20
22
|
|
|
21
23
|
describe('Collection Browser', () => {
|
|
22
24
|
beforeEach(async () => {
|
|
@@ -289,6 +291,98 @@ describe('Collection Browser', () => {
|
|
|
289
291
|
);
|
|
290
292
|
});
|
|
291
293
|
|
|
294
|
+
it('fires a separate date histogram query when year facets are applied', async () => {
|
|
295
|
+
const searchService = new MockSearchService();
|
|
296
|
+
const selectedFacets: SelectedFacets = {
|
|
297
|
+
subject: {},
|
|
298
|
+
lending: {},
|
|
299
|
+
mediatype: {},
|
|
300
|
+
language: {},
|
|
301
|
+
creator: {},
|
|
302
|
+
collection: {},
|
|
303
|
+
year: {
|
|
304
|
+
'2000': {
|
|
305
|
+
key: '2000',
|
|
306
|
+
state: 'selected',
|
|
307
|
+
count: 123,
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const el = await fixture<CollectionBrowser>(
|
|
313
|
+
html`<collection-browser .searchService=${searchService}>
|
|
314
|
+
</collection-browser>`
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
el.baseQuery = 'collection:foo';
|
|
318
|
+
el.showHistogramDatePicker = true;
|
|
319
|
+
el.selectedFacets = selectedFacets;
|
|
320
|
+
await el.updateComplete;
|
|
321
|
+
|
|
322
|
+
expect(
|
|
323
|
+
searchService.searchParams?.aggregations?.simpleParams
|
|
324
|
+
).to.deep.equal(['year']); // Explicitly requests year aggregations
|
|
325
|
+
expect(searchService.searchParams?.query).to.equal(
|
|
326
|
+
'collection:foo' // No date clause on query
|
|
327
|
+
);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('fires a separate date histogram query when a date range is applied', async () => {
|
|
331
|
+
const searchService = new MockSearchService();
|
|
332
|
+
|
|
333
|
+
const el = await fixture<CollectionBrowser>(
|
|
334
|
+
html`<collection-browser .searchService=${searchService}>
|
|
335
|
+
</collection-browser>`
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
el.baseQuery = 'collection:foo';
|
|
339
|
+
el.showHistogramDatePicker = true;
|
|
340
|
+
el.dateRangeQueryClause = 'year:[1995 TO 2005]';
|
|
341
|
+
await el.updateComplete;
|
|
342
|
+
|
|
343
|
+
expect(
|
|
344
|
+
searchService.searchParams?.aggregations?.simpleParams
|
|
345
|
+
).to.deep.equal(['year']); // Explicitly requests year aggregations
|
|
346
|
+
expect(searchService.searchParams?.query).to.equal(
|
|
347
|
+
'collection:foo' // No date clause on query
|
|
348
|
+
);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('does not fire a separate date histogram query when no date filters are applied', async () => {
|
|
352
|
+
const searchService = new MockSearchService();
|
|
353
|
+
|
|
354
|
+
const el = await fixture<CollectionBrowser>(
|
|
355
|
+
html`<collection-browser .searchService=${searchService}>
|
|
356
|
+
</collection-browser>`
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
el.baseQuery = 'collection:foo';
|
|
360
|
+
el.showHistogramDatePicker = true;
|
|
361
|
+
await el.updateComplete;
|
|
362
|
+
|
|
363
|
+
expect(searchService.searchParams?.aggregations?.simpleParams).to.satisfy(
|
|
364
|
+
(aggTypes: string[]) => !aggTypes || !aggTypes.includes('year') // Not requesting year aggregations explicitly
|
|
365
|
+
);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('does not fire a separate date histogram query when date picker is disabled', async () => {
|
|
369
|
+
const searchService = new MockSearchService();
|
|
370
|
+
|
|
371
|
+
const el = await fixture<CollectionBrowser>(
|
|
372
|
+
html`<collection-browser .searchService=${searchService}>
|
|
373
|
+
</collection-browser>`
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
el.baseQuery = 'collection:foo';
|
|
377
|
+
el.showHistogramDatePicker = false;
|
|
378
|
+
el.dateRangeQueryClause = 'year:[1995 TO 2005]';
|
|
379
|
+
await el.updateComplete;
|
|
380
|
+
|
|
381
|
+
expect(searchService.searchParams?.aggregations?.simpleParams).to.satisfy(
|
|
382
|
+
(aggTypes: string[]) => !aggTypes || !aggTypes.includes('year') // Not requesting year aggregations explicitly
|
|
383
|
+
);
|
|
384
|
+
});
|
|
385
|
+
|
|
292
386
|
it('fails gracefully if no search service provided', async () => {
|
|
293
387
|
const el = await fixture<CollectionBrowser>(
|
|
294
388
|
html`<collection-browser></collection-browser>`
|
|
@@ -381,6 +475,28 @@ describe('Collection Browser', () => {
|
|
|
381
475
|
expect(cell.model?.contentWarning).to.be.true;
|
|
382
476
|
});
|
|
383
477
|
|
|
478
|
+
it('joins full description array into a single string with line breaks', async () => {
|
|
479
|
+
const searchService = new MockSearchService();
|
|
480
|
+
|
|
481
|
+
const el = await fixture<CollectionBrowser>(
|
|
482
|
+
html`<collection-browser .searchService=${searchService}>
|
|
483
|
+
</collection-browser>`
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
// This query receives an array description like ['line1', 'line2']
|
|
487
|
+
el.baseQuery = 'multi-line-description';
|
|
488
|
+
await el.updateComplete;
|
|
489
|
+
|
|
490
|
+
const cellTemplate = el.cellForIndex(0);
|
|
491
|
+
expect(cellTemplate).to.exist;
|
|
492
|
+
|
|
493
|
+
const cell = await fixture<TileDispatcher>(cellTemplate!);
|
|
494
|
+
expect(cell).to.exist;
|
|
495
|
+
|
|
496
|
+
// Actual model description should be joined
|
|
497
|
+
expect(cell.model?.description).to.equal('line1\nline2');
|
|
498
|
+
});
|
|
499
|
+
|
|
384
500
|
it('can search on demand if only search type has changed', async () => {
|
|
385
501
|
const searchService = new MockSearchService();
|
|
386
502
|
|
|
@@ -552,6 +668,97 @@ describe('Collection Browser', () => {
|
|
|
552
668
|
expect(el.selectedSort).to.equal(SortField.title);
|
|
553
669
|
});
|
|
554
670
|
|
|
671
|
+
it('sets sort filter properties when user selects title filter', async () => {
|
|
672
|
+
const searchService = new MockSearchService();
|
|
673
|
+
const el = await fixture<CollectionBrowser>(
|
|
674
|
+
html`<collection-browser .searchService=${searchService}>
|
|
675
|
+
</collection-browser>`
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
el.baseQuery = 'collection:foo';
|
|
679
|
+
el.selectedTitleFilter = 'X';
|
|
680
|
+
await el.updateComplete;
|
|
681
|
+
|
|
682
|
+
// Wait an extra tick
|
|
683
|
+
await new Promise(res => {
|
|
684
|
+
setTimeout(res, 0);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
expect(searchService.searchParams?.query).to.equal(
|
|
688
|
+
'collection:foo AND firstTitle:X'
|
|
689
|
+
);
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
it('sets sort filter properties when user selects creator filter', async () => {
|
|
693
|
+
const searchService = new MockSearchService();
|
|
694
|
+
const el = await fixture<CollectionBrowser>(
|
|
695
|
+
html`<collection-browser .searchService=${searchService}>
|
|
696
|
+
</collection-browser>`
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
el.baseQuery = 'collection:foo';
|
|
700
|
+
el.selectedCreatorFilter = 'X';
|
|
701
|
+
await el.updateComplete;
|
|
702
|
+
|
|
703
|
+
// Wait an extra tick
|
|
704
|
+
await new Promise(res => {
|
|
705
|
+
setTimeout(res, 0);
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
expect(searchService.searchParams?.query).to.equal(
|
|
709
|
+
'collection:foo AND firstCreator:X'
|
|
710
|
+
);
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
it('sets date range query when date picker selection changed', async () => {
|
|
714
|
+
const searchService = new MockSearchService();
|
|
715
|
+
const el = await fixture<CollectionBrowser>(
|
|
716
|
+
html`<collection-browser .searchService=${searchService}>
|
|
717
|
+
</collection-browser>`
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
el.baseQuery = 'years'; // Includes year_histogram aggregation in response
|
|
721
|
+
el.showHistogramDatePicker = true;
|
|
722
|
+
await el.updateComplete;
|
|
723
|
+
|
|
724
|
+
const facets = el.shadowRoot?.querySelector(
|
|
725
|
+
'collection-facets'
|
|
726
|
+
) as CollectionFacets;
|
|
727
|
+
await facets?.updateComplete;
|
|
728
|
+
|
|
729
|
+
// Wait for the date picker to be rendered (which may take until the next tick)
|
|
730
|
+
await new Promise(res => {
|
|
731
|
+
setTimeout(res, 0);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
const histogram = facets?.shadowRoot?.querySelector(
|
|
735
|
+
'histogram-date-range'
|
|
736
|
+
) as HistogramDateRange;
|
|
737
|
+
expect(histogram).to.exist;
|
|
738
|
+
|
|
739
|
+
// Enter a new min date into the date picker
|
|
740
|
+
const minDateInput = histogram.shadowRoot?.querySelector(
|
|
741
|
+
'#date-min'
|
|
742
|
+
) as HTMLInputElement;
|
|
743
|
+
|
|
744
|
+
const pressEnterEvent = new KeyboardEvent('keyup', {
|
|
745
|
+
key: 'Enter',
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
minDateInput.value = '1960';
|
|
749
|
+
minDateInput.dispatchEvent(pressEnterEvent);
|
|
750
|
+
|
|
751
|
+
// Wait for the histogram's update delay
|
|
752
|
+
await new Promise(res => {
|
|
753
|
+
setTimeout(res, histogram.updateDelay + 50);
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
// Ensure that the histogram change propagated to the collection browser's
|
|
757
|
+
// date query correctly.
|
|
758
|
+
await el.updateComplete;
|
|
759
|
+
expect(el.dateRangeQueryClause).to.equal('year:[1960 TO 2000]');
|
|
760
|
+
});
|
|
761
|
+
|
|
555
762
|
it('scrolls to page', async () => {
|
|
556
763
|
const searchService = new MockSearchService();
|
|
557
764
|
const el = await fixture<CollectionBrowser>(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Result } from '@internetarchive/result-type';
|
|
2
2
|
import {
|
|
3
|
+
Aggregation,
|
|
3
4
|
ItemHit,
|
|
4
5
|
SearchResponse,
|
|
5
6
|
SearchServiceError,
|
|
@@ -40,6 +41,51 @@ export const mockSuccessSingleResult: Result<
|
|
|
40
41
|
},
|
|
41
42
|
};
|
|
42
43
|
|
|
44
|
+
export const mockSuccessWithYearHistogramAggs: Result<
|
|
45
|
+
SearchResponse,
|
|
46
|
+
SearchServiceError
|
|
47
|
+
> = {
|
|
48
|
+
success: {
|
|
49
|
+
request: {
|
|
50
|
+
clientParameters: {
|
|
51
|
+
user_query: 'years',
|
|
52
|
+
sort: [],
|
|
53
|
+
},
|
|
54
|
+
finalizedParameters: {
|
|
55
|
+
user_query: 'years',
|
|
56
|
+
sort: [],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
rawResponse: {},
|
|
60
|
+
response: {
|
|
61
|
+
totalResults: 1,
|
|
62
|
+
returnedCount: 1,
|
|
63
|
+
aggregations: {
|
|
64
|
+
subject: new Aggregation({
|
|
65
|
+
buckets: [
|
|
66
|
+
{
|
|
67
|
+
key: 'foo',
|
|
68
|
+
doc_count: 3,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
}),
|
|
72
|
+
year_histogram: new Aggregation({
|
|
73
|
+
buckets: [1, 2, 3, 3, 2, 1],
|
|
74
|
+
first_bucket_key: 1950,
|
|
75
|
+
last_bucket_key: 2000,
|
|
76
|
+
interval: 10,
|
|
77
|
+
number_buckets: 6,
|
|
78
|
+
}),
|
|
79
|
+
},
|
|
80
|
+
results: [],
|
|
81
|
+
},
|
|
82
|
+
responseHeader: {
|
|
83
|
+
succeeded: true,
|
|
84
|
+
query_time: 0,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
43
89
|
export const mockSuccessLoggedInResult: Result<
|
|
44
90
|
SearchResponse,
|
|
45
91
|
SearchServiceError
|
|
@@ -224,3 +270,39 @@ export const mockSuccessMultipleResults: Result<
|
|
|
224
270
|
},
|
|
225
271
|
},
|
|
226
272
|
};
|
|
273
|
+
|
|
274
|
+
export const mockSuccessMultiLineDescription: Result<
|
|
275
|
+
SearchResponse,
|
|
276
|
+
SearchServiceError
|
|
277
|
+
> = {
|
|
278
|
+
success: {
|
|
279
|
+
request: {
|
|
280
|
+
clientParameters: {
|
|
281
|
+
user_query: 'multi-line-description',
|
|
282
|
+
sort: [],
|
|
283
|
+
},
|
|
284
|
+
finalizedParameters: {
|
|
285
|
+
user_query: 'multi-line-description',
|
|
286
|
+
sort: [],
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
rawResponse: {},
|
|
290
|
+
response: {
|
|
291
|
+
totalResults: 1,
|
|
292
|
+
returnedCount: 1,
|
|
293
|
+
results: [
|
|
294
|
+
new ItemHit({
|
|
295
|
+
fields: {
|
|
296
|
+
identifier: 'foo',
|
|
297
|
+
collection: ['foo', 'bar'],
|
|
298
|
+
description: ['line1', 'line2'],
|
|
299
|
+
},
|
|
300
|
+
}),
|
|
301
|
+
],
|
|
302
|
+
},
|
|
303
|
+
responseHeader: {
|
|
304
|
+
succeeded: true,
|
|
305
|
+
query_time: 0,
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
};
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
mockSuccessLoggedInResult,
|
|
14
14
|
mockSuccessNoPreviewResult,
|
|
15
15
|
mockSuccessLoggedInAndNoPreviewResult,
|
|
16
|
+
mockSuccessWithYearHistogramAggs,
|
|
17
|
+
mockSuccessMultiLineDescription,
|
|
16
18
|
} from './mock-search-responses';
|
|
17
19
|
|
|
18
20
|
export class MockSearchService implements SearchServiceInterface {
|
|
@@ -46,6 +48,10 @@ export class MockSearchService implements SearchServiceInterface {
|
|
|
46
48
|
switch (this.searchParams?.query) {
|
|
47
49
|
case 'single-result':
|
|
48
50
|
return mockSuccessSingleResult;
|
|
51
|
+
case 'years':
|
|
52
|
+
return mockSuccessWithYearHistogramAggs;
|
|
53
|
+
case 'multi-line-description':
|
|
54
|
+
return mockSuccessMultiLineDescription;
|
|
49
55
|
case 'loggedin':
|
|
50
56
|
return mockSuccessLoggedInResult;
|
|
51
57
|
case 'no-preview':
|
|
@@ -105,4 +105,44 @@ describe('List Tile', () => {
|
|
|
105
105
|
expect(dateRow).to.exist;
|
|
106
106
|
expect(dateRow?.textContent?.trim()).to.contain('Added: Jan 01, 2010');
|
|
107
107
|
});
|
|
108
|
+
|
|
109
|
+
it('should render links to /search pages (not search.php) for subject, creator, and source', async () => {
|
|
110
|
+
const model: Partial<TileModel> = {
|
|
111
|
+
subjects: ['foo'],
|
|
112
|
+
creators: ['bar'],
|
|
113
|
+
source: 'baz',
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const el = await fixture<TileList>(html`
|
|
117
|
+
<tile-list .model=${model}></tile-list>
|
|
118
|
+
`);
|
|
119
|
+
|
|
120
|
+
const subjectLink = el.shadowRoot?.querySelector('#topics a[href]');
|
|
121
|
+
expect(subjectLink).to.exist;
|
|
122
|
+
expect(subjectLink?.getAttribute('href')).to.equal(
|
|
123
|
+
`/search?query=${encodeURIComponent('subject:"foo"')}`
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const creatorLink = el.shadowRoot?.querySelector('#creator a[href]');
|
|
127
|
+
expect(creatorLink).to.exist;
|
|
128
|
+
expect(creatorLink?.getAttribute('href')).to.equal(
|
|
129
|
+
`/search?query=${encodeURIComponent('creator:"bar"')}`
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const sourceLink = el.shadowRoot?.querySelector('#source a[href]');
|
|
133
|
+
expect(sourceLink).to.exist;
|
|
134
|
+
expect(sourceLink?.getAttribute('href')).to.equal(
|
|
135
|
+
`/search?query=${encodeURIComponent('source:"baz"')}`
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should render multi-line descriptions with spaces b/w lines', async () => {
|
|
140
|
+
const el = await fixture<TileList>(html`
|
|
141
|
+
<tile-list .model=${{ description: 'line1\nline2' }}> </tile-list>
|
|
142
|
+
`);
|
|
143
|
+
|
|
144
|
+
const descriptionBlock = el.shadowRoot?.getElementById('description');
|
|
145
|
+
expect(descriptionBlock).to.exist;
|
|
146
|
+
expect(descriptionBlock?.textContent?.trim()).to.equal('line1 line2'); // line break replaced by space
|
|
147
|
+
});
|
|
108
148
|
});
|