@internetarchive/collection-browser 0.3.4 → 0.3.5
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 +4 -0
- package/dist/src/app-root.js +99 -53
- package/dist/src/app-root.js.map +1 -1
- package/dist/src/collection-browser.d.ts +1 -2
- package/dist/src/collection-browser.js +21 -49
- package/dist/src/collection-browser.js.map +1 -1
- package/dist/src/collection-facets/facet-tombstone-row.d.ts +5 -0
- package/dist/src/collection-facets/facet-tombstone-row.js +43 -0
- package/dist/src/collection-facets/facet-tombstone-row.js.map +1 -0
- package/dist/src/collection-facets/facets-template.js +5 -3
- package/dist/src/collection-facets/facets-template.js.map +1 -1
- package/dist/src/collection-facets.d.ts +2 -0
- package/dist/src/collection-facets.js +44 -18
- package/dist/src/collection-facets.js.map +1 -1
- package/dist/src/models.d.ts +1 -0
- package/dist/src/models.js.map +1 -1
- package/dist/src/restoration-state-handler.d.ts +2 -1
- package/dist/src/restoration-state-handler.js +10 -0
- package/dist/src/restoration-state-handler.js.map +1 -1
- package/dist/src/tiles/grid/tile-stats.js +11 -5
- package/dist/src/tiles/grid/tile-stats.js.map +1 -1
- package/dist/src/tiles/list/tile-list-compact.d.ts +1 -0
- package/dist/src/tiles/list/tile-list-compact.js +8 -4
- package/dist/src/tiles/list/tile-list-compact.js.map +1 -1
- package/dist/src/tiles/list/tile-list.js +5 -2
- package/dist/src/tiles/list/tile-list.js.map +1 -1
- package/dist/src/tiles/mediatype-icon.js +2 -0
- package/dist/src/tiles/mediatype-icon.js.map +1 -1
- package/dist/test/collection-browser.test.js +125 -3
- package/dist/test/collection-browser.test.js.map +1 -1
- package/dist/test/collection-facets/facets-template.test.js +4 -4
- package/dist/test/collection-facets/facets-template.test.js.map +1 -1
- package/dist/test/mocks/mock-search-responses.d.ts +3 -0
- package/dist/test/mocks/mock-search-responses.js +95 -0
- package/dist/test/mocks/mock-search-responses.js.map +1 -1
- package/dist/test/mocks/mock-search-service.js +15 -8
- package/dist/test/mocks/mock-search-service.js.map +1 -1
- package/dist/test/restoration-state-handler.test.js +9 -0
- package/dist/test/restoration-state-handler.test.js.map +1 -1
- package/dist/test/tiles/list/tile-list-compact.test.js +99 -0
- package/dist/test/tiles/list/tile-list-compact.test.js.map +1 -1
- package/dist/test/tiles/list/tile-list.test.js +32 -0
- package/dist/test/tiles/list/tile-list.test.js.map +1 -1
- package/package.json +3 -3
- package/src/app-root.ts +104 -55
- package/src/collection-browser.ts +26 -48
- package/src/collection-facets/facet-tombstone-row.ts +40 -0
- package/src/collection-facets/facets-template.ts +5 -3
- package/src/collection-facets.ts +49 -18
- package/src/models.ts +1 -0
- package/src/restoration-state-handler.ts +19 -1
- package/src/tiles/grid/tile-stats.ts +18 -5
- package/src/tiles/list/tile-list-compact.ts +7 -3
- package/src/tiles/list/tile-list.ts +6 -1
- package/src/tiles/mediatype-icon.ts +2 -0
- package/test/collection-browser.test.ts +169 -3
- package/test/collection-facets/facets-template.test.ts +5 -3
- package/test/mocks/mock-search-responses.ts +107 -0
- package/test/mocks/mock-search-service.ts +16 -8
- package/test/restoration-state-handler.test.ts +12 -0
- package/test/tiles/list/tile-list-compact.test.ts +110 -0
- package/test/tiles/list/tile-list.test.ts +36 -0
|
@@ -21,6 +21,18 @@ export class TileStats extends LitElement {
|
|
|
21
21
|
@property({ type: Number }) commentCount?: number;
|
|
22
22
|
|
|
23
23
|
render() {
|
|
24
|
+
const formattedFavCount = formatCount(this.favCount, 'short', 'short');
|
|
25
|
+
const formattedReviewCount = formatCount(
|
|
26
|
+
this.commentCount,
|
|
27
|
+
'short',
|
|
28
|
+
'short'
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const uploadsOrViewsTitle =
|
|
32
|
+
this.mediatype === 'account'
|
|
33
|
+
? `${this.itemCount} uploads`
|
|
34
|
+
: `${this.viewCount} all-time views`;
|
|
35
|
+
|
|
24
36
|
return html`
|
|
25
37
|
<div class="item-stats">
|
|
26
38
|
<p class="sr-only">
|
|
@@ -31,7 +43,7 @@ export class TileStats extends LitElement {
|
|
|
31
43
|
<p class="sr-only">Mediatype:</p>
|
|
32
44
|
<mediatype-icon .mediatype=${this.mediatype}></mediatype-icon>
|
|
33
45
|
</li>
|
|
34
|
-
<li class="col">
|
|
46
|
+
<li class="col" title="${uploadsOrViewsTitle}">
|
|
35
47
|
${this.mediatype === 'account' ? uploadIcon : viewsIcon}
|
|
36
48
|
<p class="status-text">
|
|
37
49
|
<span class="sr-only">
|
|
@@ -44,18 +56,18 @@ export class TileStats extends LitElement {
|
|
|
44
56
|
)}
|
|
45
57
|
</p>
|
|
46
58
|
</li>
|
|
47
|
-
<li class="col">
|
|
59
|
+
<li class="col" title="${formattedFavCount} favorites">
|
|
48
60
|
${favoriteFilledIcon}
|
|
49
61
|
<p class="status-text">
|
|
50
62
|
<span class="sr-only">Favorites:</span>
|
|
51
|
-
${
|
|
63
|
+
${formattedFavCount}
|
|
52
64
|
</p>
|
|
53
65
|
</li>
|
|
54
|
-
<li class="col">
|
|
66
|
+
<li class="col" title="${formattedReviewCount} reviews">
|
|
55
67
|
${reviewsIcon}
|
|
56
68
|
<p class="status-text">
|
|
57
69
|
<span class="sr-only">Reviews:</span>
|
|
58
|
-
${
|
|
70
|
+
${formattedReviewCount}
|
|
59
71
|
</p>
|
|
60
72
|
</li>
|
|
61
73
|
</ul>
|
|
@@ -116,6 +128,7 @@ export class TileStats extends LitElement {
|
|
|
116
128
|
width: 10px;
|
|
117
129
|
display: block;
|
|
118
130
|
margin: auto;
|
|
131
|
+
pointer-events: none;
|
|
119
132
|
}
|
|
120
133
|
|
|
121
134
|
.status-text {
|
|
@@ -55,9 +55,7 @@ export class TileListCompact extends LitElement {
|
|
|
55
55
|
>
|
|
56
56
|
</mediatype-icon>
|
|
57
57
|
</div>
|
|
58
|
-
<div id="views">
|
|
59
|
-
${formatCount(this.model?.viewCount ?? 0, this.formatSize)}
|
|
60
|
-
</div>
|
|
58
|
+
<div id="views">${formatCount(this.views ?? 0, this.formatSize)}</div>
|
|
61
59
|
</div>
|
|
62
60
|
`;
|
|
63
61
|
}
|
|
@@ -80,6 +78,12 @@ export class TileListCompact extends LitElement {
|
|
|
80
78
|
}
|
|
81
79
|
}
|
|
82
80
|
|
|
81
|
+
private get views(): number | undefined {
|
|
82
|
+
return this.sortParam?.field === 'week'
|
|
83
|
+
? this.model?.weeklyViewCount // weekly views
|
|
84
|
+
: this.model?.viewCount; // all-time views
|
|
85
|
+
}
|
|
86
|
+
|
|
83
87
|
private get classSize(): string {
|
|
84
88
|
if (
|
|
85
89
|
this.mobileBreakpoint &&
|
|
@@ -247,8 +247,13 @@ export class TileList extends LitElement {
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
private get viewsTemplate() {
|
|
250
|
+
const viewCount =
|
|
251
|
+
this.sortParam?.field === 'week'
|
|
252
|
+
? this.model?.weeklyViewCount // weekly views
|
|
253
|
+
: this.model?.viewCount; // all-time views
|
|
254
|
+
|
|
250
255
|
return this.metadataTemplate(
|
|
251
|
-
`${formatCount(
|
|
256
|
+
`${formatCount(viewCount ?? 0, this.formatSize)}`,
|
|
252
257
|
'Views'
|
|
253
258
|
);
|
|
254
259
|
}
|
|
@@ -40,6 +40,7 @@ export class MediatypeIcon extends LitElement {
|
|
|
40
40
|
<div
|
|
41
41
|
id="icon"
|
|
42
42
|
class="${this.showText ? 'show-text' : 'hide-text'}"
|
|
43
|
+
title="${config.text}"
|
|
43
44
|
style="--iconFillColor: ${config.color}"
|
|
44
45
|
>
|
|
45
46
|
${config.icon}
|
|
@@ -69,6 +70,7 @@ export class MediatypeIcon extends LitElement {
|
|
|
69
70
|
svg {
|
|
70
71
|
height: var(--iconHeight, 10px);
|
|
71
72
|
width: var(--iconWidth, 10px);
|
|
73
|
+
pointer-events: none;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
.fill-color {
|
|
@@ -16,8 +16,24 @@ import { MockSearchService } from './mocks/mock-search-service';
|
|
|
16
16
|
import { MockCollectionNameCache } from './mocks/mock-collection-name-cache';
|
|
17
17
|
import { MockAnalyticsHandler } from './mocks/mock-analytics-handler';
|
|
18
18
|
import { analyticsCategories } from '../src/utils/analytics-events';
|
|
19
|
+
import type { TileDispatcher } from '../src/tiles/tile-dispatcher';
|
|
19
20
|
|
|
20
21
|
describe('Collection Browser', () => {
|
|
22
|
+
beforeEach(async () => {
|
|
23
|
+
// Apparently query params set by one test can bleed into other tests.
|
|
24
|
+
// Since collection browser restores its state from certain query params, we need
|
|
25
|
+
// to clear these before each test to ensure they run in isolation from one another.
|
|
26
|
+
const url = new URL(window.location.href);
|
|
27
|
+
const { searchParams } = url;
|
|
28
|
+
searchParams.delete('sin');
|
|
29
|
+
searchParams.delete('sort');
|
|
30
|
+
searchParams.delete('query');
|
|
31
|
+
searchParams.delete('page');
|
|
32
|
+
searchParams.delete('and[]');
|
|
33
|
+
searchParams.delete('not[]');
|
|
34
|
+
window.history.replaceState({}, '', url);
|
|
35
|
+
});
|
|
36
|
+
|
|
21
37
|
it('clear existing filter for facets & sort-bar', async () => {
|
|
22
38
|
const el = await fixture<CollectionBrowser>(
|
|
23
39
|
html`<collection-browser></collection-browser>`
|
|
@@ -153,8 +169,11 @@ describe('Collection Browser', () => {
|
|
|
153
169
|
});
|
|
154
170
|
|
|
155
171
|
it('should render with a sort bar, facets, and infinite scroller', async () => {
|
|
172
|
+
const searchService = new MockSearchService();
|
|
173
|
+
|
|
156
174
|
const el = await fixture<CollectionBrowser>(
|
|
157
|
-
html`<collection-browser
|
|
175
|
+
html`<collection-browser .searchService=${searchService}>
|
|
176
|
+
</collection-browser>`
|
|
158
177
|
);
|
|
159
178
|
|
|
160
179
|
el.baseQuery = 'hello';
|
|
@@ -227,6 +246,141 @@ describe('Collection Browser', () => {
|
|
|
227
246
|
).to.contains('Results');
|
|
228
247
|
});
|
|
229
248
|
|
|
249
|
+
it('queries the search service with facets selected/negated', async () => {
|
|
250
|
+
const searchService = new MockSearchService();
|
|
251
|
+
const selectedFacets: SelectedFacets = {
|
|
252
|
+
subject: {
|
|
253
|
+
foo: {
|
|
254
|
+
key: 'foo',
|
|
255
|
+
count: 1,
|
|
256
|
+
state: 'selected',
|
|
257
|
+
},
|
|
258
|
+
bar: {
|
|
259
|
+
key: 'bar',
|
|
260
|
+
count: 2,
|
|
261
|
+
state: 'hidden',
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
lending: {},
|
|
265
|
+
mediatype: {},
|
|
266
|
+
language: {
|
|
267
|
+
en: {
|
|
268
|
+
key: 'en',
|
|
269
|
+
count: 1,
|
|
270
|
+
state: 'selected',
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
creator: {},
|
|
274
|
+
collection: {},
|
|
275
|
+
year: {},
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const el = await fixture<CollectionBrowser>(
|
|
279
|
+
html`<collection-browser .searchService=${searchService}>
|
|
280
|
+
</collection-browser>`
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
el.baseQuery = 'collection:foo';
|
|
284
|
+
el.selectedFacets = selectedFacets;
|
|
285
|
+
await el.updateComplete;
|
|
286
|
+
|
|
287
|
+
expect(searchService.searchParams?.query).to.equal(
|
|
288
|
+
'collection:foo AND (subject:("foo" OR -"bar") AND language:("en"))'
|
|
289
|
+
);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('fails gracefully if no search service provided', async () => {
|
|
293
|
+
const el = await fixture<CollectionBrowser>(
|
|
294
|
+
html`<collection-browser></collection-browser>`
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
el.baseQuery = 'collection:foo';
|
|
298
|
+
await el.updateComplete;
|
|
299
|
+
|
|
300
|
+
// This shouldn't throw an error
|
|
301
|
+
expect(el.fetchPage(2)).to.exist;
|
|
302
|
+
|
|
303
|
+
// Should continue showing the empty placeholder
|
|
304
|
+
expect(el.shadowRoot?.querySelector('empty-placeholder')).to.exist;
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('restores search type from URL param', async () => {
|
|
308
|
+
// Add a sin=TXT param to the URL
|
|
309
|
+
const url = new URL(window.location.href);
|
|
310
|
+
url.searchParams.append('sin', 'TXT');
|
|
311
|
+
window.history.replaceState({}, '', url);
|
|
312
|
+
|
|
313
|
+
const searchService = new MockSearchService();
|
|
314
|
+
|
|
315
|
+
const el = await fixture<CollectionBrowser>(
|
|
316
|
+
html`<collection-browser .searchService=${searchService}>
|
|
317
|
+
</collection-browser>`
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
expect(el.searchType).to.equal(SearchType.FULLTEXT);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('applies loggedin flag to tile models if needed', async () => {
|
|
324
|
+
const searchService = new MockSearchService();
|
|
325
|
+
|
|
326
|
+
const el = await fixture<CollectionBrowser>(
|
|
327
|
+
html`<collection-browser .searchService=${searchService}>
|
|
328
|
+
</collection-browser>`
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
el.baseQuery = 'loggedin';
|
|
332
|
+
await el.updateComplete;
|
|
333
|
+
|
|
334
|
+
const cellTemplate = el.cellForIndex(0);
|
|
335
|
+
expect(cellTemplate).to.exist;
|
|
336
|
+
|
|
337
|
+
const cell = await fixture<TileDispatcher>(cellTemplate!);
|
|
338
|
+
expect(cell).to.exist;
|
|
339
|
+
|
|
340
|
+
expect(cell.model?.loginRequired).to.be.true;
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it('applies no-preview flag to tile models if needed', async () => {
|
|
344
|
+
const searchService = new MockSearchService();
|
|
345
|
+
|
|
346
|
+
const el = await fixture<CollectionBrowser>(
|
|
347
|
+
html`<collection-browser .searchService=${searchService}>
|
|
348
|
+
</collection-browser>`
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
el.baseQuery = 'no-preview';
|
|
352
|
+
await el.updateComplete;
|
|
353
|
+
|
|
354
|
+
const cellTemplate = el.cellForIndex(0);
|
|
355
|
+
expect(cellTemplate).to.exist;
|
|
356
|
+
|
|
357
|
+
const cell = await fixture<TileDispatcher>(cellTemplate!);
|
|
358
|
+
expect(cell).to.exist;
|
|
359
|
+
|
|
360
|
+
expect(cell.model?.contentWarning).to.be.true;
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('both loggedin and no-preview flags can be set simultaneously', async () => {
|
|
364
|
+
const searchService = new MockSearchService();
|
|
365
|
+
|
|
366
|
+
const el = await fixture<CollectionBrowser>(
|
|
367
|
+
html`<collection-browser .searchService=${searchService}>
|
|
368
|
+
</collection-browser>`
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
el.baseQuery = 'loggedin-no-preview';
|
|
372
|
+
await el.updateComplete;
|
|
373
|
+
|
|
374
|
+
const cellTemplate = el.cellForIndex(0);
|
|
375
|
+
expect(cellTemplate).to.exist;
|
|
376
|
+
|
|
377
|
+
const cell = await fixture<TileDispatcher>(cellTemplate!);
|
|
378
|
+
expect(cell).to.exist;
|
|
379
|
+
|
|
380
|
+
expect(cell.model?.loginRequired).to.be.true;
|
|
381
|
+
expect(cell.model?.contentWarning).to.be.true;
|
|
382
|
+
});
|
|
383
|
+
|
|
230
384
|
it('can search on demand if only search type has changed', async () => {
|
|
231
385
|
const searchService = new MockSearchService();
|
|
232
386
|
|
|
@@ -370,8 +524,10 @@ describe('Collection Browser', () => {
|
|
|
370
524
|
});
|
|
371
525
|
|
|
372
526
|
it('sets sort properties when user changes sort', async () => {
|
|
527
|
+
const searchService = new MockSearchService();
|
|
373
528
|
const el = await fixture<CollectionBrowser>(
|
|
374
|
-
html`<collection-browser
|
|
529
|
+
html`<collection-browser .searchService=${searchService}>
|
|
530
|
+
</collection-browser>`
|
|
375
531
|
);
|
|
376
532
|
|
|
377
533
|
expect(el.selectedSort).to.equal(SortField.relevance);
|
|
@@ -397,10 +553,16 @@ describe('Collection Browser', () => {
|
|
|
397
553
|
});
|
|
398
554
|
|
|
399
555
|
it('scrolls to page', async () => {
|
|
556
|
+
const searchService = new MockSearchService();
|
|
400
557
|
const el = await fixture<CollectionBrowser>(
|
|
401
|
-
html`<collection-browser
|
|
558
|
+
html`<collection-browser .searchService=${searchService}>
|
|
559
|
+
</collection-browser>`
|
|
402
560
|
);
|
|
403
561
|
|
|
562
|
+
// Infinite scroller won't exist unless there's a base query
|
|
563
|
+
el.baseQuery = 'collection:foo';
|
|
564
|
+
await el.updateComplete;
|
|
565
|
+
|
|
404
566
|
const infiniteScroller = el.shadowRoot?.querySelector(
|
|
405
567
|
'infinite-scroller'
|
|
406
568
|
) as InfiniteScroller;
|
|
@@ -435,6 +597,10 @@ describe('Collection Browser', () => {
|
|
|
435
597
|
);
|
|
436
598
|
const infiniteScrollerRefreshSpy = sinon.spy();
|
|
437
599
|
|
|
600
|
+
// Infinite scroller won't exist unless there's a base query
|
|
601
|
+
el.baseQuery = 'collection:foo';
|
|
602
|
+
await el.updateComplete;
|
|
603
|
+
|
|
438
604
|
const infiniteScroller = el.shadowRoot?.querySelector('infinite-scroller');
|
|
439
605
|
(infiniteScroller as InfiniteScroller).reload = infiniteScrollerRefreshSpy;
|
|
440
606
|
expect(infiniteScrollerRefreshSpy.called).to.be.false;
|
|
@@ -49,17 +49,19 @@ describe('Render facets', () => {
|
|
|
49
49
|
expect(el.shadowRoot?.querySelector('.facets-on-modal')).to.exist;
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
it('find facet-title and facet-count for
|
|
52
|
+
it('find facet-title and facet-count for particular facet group', async () => {
|
|
53
53
|
const el = await fixture<FacetsTemplate>(
|
|
54
54
|
html`<facets-template .facetGroup=${facetGroup}></facets-template>`
|
|
55
55
|
);
|
|
56
56
|
await el.updateComplete;
|
|
57
57
|
|
|
58
58
|
const facetInfo = el.shadowRoot?.querySelector('.facet-info-display');
|
|
59
|
-
expect(facetInfo?.querySelector('.facet-title')?.textContent).equal(
|
|
59
|
+
expect(facetInfo?.querySelector('.facet-title')?.textContent).to.equal(
|
|
60
60
|
'audio'
|
|
61
61
|
);
|
|
62
|
-
expect(
|
|
62
|
+
expect(
|
|
63
|
+
facetInfo?.querySelector('.facet-count')?.textContent?.trim()
|
|
64
|
+
).to.equal('1,001');
|
|
63
65
|
});
|
|
64
66
|
|
|
65
67
|
it('find the hidden facet item', async () => {
|
|
@@ -40,6 +40,113 @@ export const mockSuccessSingleResult: Result<
|
|
|
40
40
|
},
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
export const mockSuccessLoggedInResult: Result<
|
|
44
|
+
SearchResponse,
|
|
45
|
+
SearchServiceError
|
|
46
|
+
> = {
|
|
47
|
+
success: {
|
|
48
|
+
request: {
|
|
49
|
+
clientParameters: {
|
|
50
|
+
user_query: 'loggedin',
|
|
51
|
+
sort: [],
|
|
52
|
+
},
|
|
53
|
+
finalizedParameters: {
|
|
54
|
+
user_query: 'loggedin',
|
|
55
|
+
sort: [],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
rawResponse: {},
|
|
59
|
+
response: {
|
|
60
|
+
totalResults: 1,
|
|
61
|
+
returnedCount: 1,
|
|
62
|
+
results: [
|
|
63
|
+
new ItemHit({
|
|
64
|
+
fields: {
|
|
65
|
+
identifier: 'foo',
|
|
66
|
+
collection: ['foo', 'loggedin', 'bar'],
|
|
67
|
+
title: 'foo',
|
|
68
|
+
mediatype: 'data',
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
responseHeader: {
|
|
74
|
+
succeeded: true,
|
|
75
|
+
query_time: 0,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const mockSuccessNoPreviewResult: Result<
|
|
81
|
+
SearchResponse,
|
|
82
|
+
SearchServiceError
|
|
83
|
+
> = {
|
|
84
|
+
success: {
|
|
85
|
+
request: {
|
|
86
|
+
clientParameters: {
|
|
87
|
+
user_query: 'no-preview',
|
|
88
|
+
sort: [],
|
|
89
|
+
},
|
|
90
|
+
finalizedParameters: {
|
|
91
|
+
user_query: 'no-preview',
|
|
92
|
+
sort: [],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
rawResponse: {},
|
|
96
|
+
response: {
|
|
97
|
+
totalResults: 1,
|
|
98
|
+
returnedCount: 1,
|
|
99
|
+
results: [
|
|
100
|
+
new ItemHit({
|
|
101
|
+
fields: {
|
|
102
|
+
identifier: 'foo',
|
|
103
|
+
collection: ['foo', 'no-preview', 'bar'],
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
responseHeader: {
|
|
109
|
+
succeeded: true,
|
|
110
|
+
query_time: 0,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const mockSuccessLoggedInAndNoPreviewResult: Result<
|
|
116
|
+
SearchResponse,
|
|
117
|
+
SearchServiceError
|
|
118
|
+
> = {
|
|
119
|
+
success: {
|
|
120
|
+
request: {
|
|
121
|
+
clientParameters: {
|
|
122
|
+
user_query: 'loggedin-no-preview',
|
|
123
|
+
sort: [],
|
|
124
|
+
},
|
|
125
|
+
finalizedParameters: {
|
|
126
|
+
user_query: 'loggedin-no-preview',
|
|
127
|
+
sort: [],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
rawResponse: {},
|
|
131
|
+
response: {
|
|
132
|
+
totalResults: 1,
|
|
133
|
+
returnedCount: 1,
|
|
134
|
+
results: [
|
|
135
|
+
new ItemHit({
|
|
136
|
+
fields: {
|
|
137
|
+
identifier: 'foo',
|
|
138
|
+
collection: ['foo', 'loggedin', 'no-preview', 'bar'],
|
|
139
|
+
},
|
|
140
|
+
}),
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
responseHeader: {
|
|
144
|
+
succeeded: true,
|
|
145
|
+
query_time: 0,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
43
150
|
export const getMockSuccessSingleResultWithSort: (
|
|
44
151
|
resultsSpy: Function
|
|
45
152
|
) => Result<SearchResponse, SearchServiceError> = (resultsSpy: Function) => ({
|
|
@@ -10,6 +10,9 @@ import {
|
|
|
10
10
|
mockSuccessSingleResult,
|
|
11
11
|
mockSuccessMultipleResults,
|
|
12
12
|
getMockSuccessSingleResultWithSort,
|
|
13
|
+
mockSuccessLoggedInResult,
|
|
14
|
+
mockSuccessNoPreviewResult,
|
|
15
|
+
mockSuccessLoggedInAndNoPreviewResult,
|
|
13
16
|
} from './mock-search-responses';
|
|
14
17
|
|
|
15
18
|
export class MockSearchService implements SearchServiceInterface {
|
|
@@ -40,14 +43,19 @@ export class MockSearchService implements SearchServiceInterface {
|
|
|
40
43
|
});
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
switch (this.searchParams?.query) {
|
|
47
|
+
case 'single-result':
|
|
48
|
+
return mockSuccessSingleResult;
|
|
49
|
+
case 'loggedin':
|
|
50
|
+
return mockSuccessLoggedInResult;
|
|
51
|
+
case 'no-preview':
|
|
52
|
+
return mockSuccessNoPreviewResult;
|
|
53
|
+
case 'loggedin-no-preview':
|
|
54
|
+
return mockSuccessLoggedInAndNoPreviewResult;
|
|
55
|
+
case 'with-sort':
|
|
56
|
+
return getMockSuccessSingleResultWithSort(this.resultsSpy);
|
|
57
|
+
default:
|
|
58
|
+
return mockSuccessMultipleResults;
|
|
45
59
|
}
|
|
46
|
-
|
|
47
|
-
if (this.searchParams?.query === 'with-sort') {
|
|
48
|
-
return getMockSuccessSingleResultWithSort(this.resultsSpy);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return mockSuccessMultipleResults;
|
|
52
60
|
}
|
|
53
61
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SearchType } from '@internetarchive/search-service';
|
|
1
2
|
import { expect } from '@open-wc/testing';
|
|
2
3
|
import { RestorationStateHandler } from '../src/restoration-state-handler';
|
|
3
4
|
|
|
@@ -13,6 +14,17 @@ describe('Restoration state handler', () => {
|
|
|
13
14
|
expect(restorationState.baseQuery).to.equal('boop');
|
|
14
15
|
});
|
|
15
16
|
|
|
17
|
+
it('should restore full text search type from URL', async () => {
|
|
18
|
+
const handler = new RestorationStateHandler({ context: 'search' });
|
|
19
|
+
|
|
20
|
+
const url = new URL(window.location.href);
|
|
21
|
+
url.search = '?sin=TXT';
|
|
22
|
+
window.history.replaceState({ path: url.href }, '', url.href);
|
|
23
|
+
|
|
24
|
+
const restorationState = handler.getRestorationState();
|
|
25
|
+
expect(restorationState.searchType).to.equal(SearchType.FULLTEXT);
|
|
26
|
+
});
|
|
27
|
+
|
|
16
28
|
it('should restore page number from URL', async () => {
|
|
17
29
|
const handler = new RestorationStateHandler({ context: 'search' });
|
|
18
30
|
|
|
@@ -4,6 +4,7 @@ import { html } from 'lit';
|
|
|
4
4
|
import type { TileListCompact } from '../../../src/tiles/list/tile-list-compact';
|
|
5
5
|
|
|
6
6
|
import '../../../src/tiles/list/tile-list-compact';
|
|
7
|
+
import type { TileModel } from '../../../src/models';
|
|
7
8
|
|
|
8
9
|
describe('List Tile Compact', () => {
|
|
9
10
|
it('should render initial component', async () => {
|
|
@@ -35,4 +36,113 @@ describe('List Tile Compact', () => {
|
|
|
35
36
|
|
|
36
37
|
expect(creator).to.exist;
|
|
37
38
|
});
|
|
39
|
+
|
|
40
|
+
it('should render weekly views when sorting by week', async () => {
|
|
41
|
+
const el = await fixture<TileListCompact>(html`
|
|
42
|
+
<tile-list-compact
|
|
43
|
+
.model=${{ viewCount: 50, weeklyViewCount: 10 }}
|
|
44
|
+
.sortParam=${{ field: 'week', direction: 'desc' }}
|
|
45
|
+
>
|
|
46
|
+
</tile-list-compact>
|
|
47
|
+
`);
|
|
48
|
+
|
|
49
|
+
const viewsColumn = el.shadowRoot?.getElementById('views');
|
|
50
|
+
expect(viewsColumn).to.exist;
|
|
51
|
+
expect(viewsColumn?.textContent?.trim()).to.equal('10');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should render 0 for views if missing model', async () => {
|
|
55
|
+
const el = await fixture<TileListCompact>(html`
|
|
56
|
+
<tile-list-compact .sortParam=${{ field: 'week', direction: 'desc' }}>
|
|
57
|
+
</tile-list-compact>
|
|
58
|
+
`);
|
|
59
|
+
|
|
60
|
+
const viewsColumn = el.shadowRoot?.getElementById('views');
|
|
61
|
+
expect(viewsColumn).to.exist;
|
|
62
|
+
expect(viewsColumn?.textContent?.trim()).to.equal('0');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should render published date when sorting by it', async () => {
|
|
66
|
+
const model: Partial<TileModel> = {
|
|
67
|
+
dateAdded: new Date('2010-01-01'),
|
|
68
|
+
dateArchived: new Date('2011-01-01'),
|
|
69
|
+
datePublished: new Date('2012-01-01'),
|
|
70
|
+
dateReviewed: new Date('2013-01-01'),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const el = await fixture<TileListCompact>(html`
|
|
74
|
+
<tile-list-compact
|
|
75
|
+
.model=${model}
|
|
76
|
+
.sortParam=${{ field: 'date', direction: 'desc' }}
|
|
77
|
+
>
|
|
78
|
+
</tile-list-compact>
|
|
79
|
+
`);
|
|
80
|
+
|
|
81
|
+
const dateColumn = el.shadowRoot?.getElementById('date');
|
|
82
|
+
expect(dateColumn).to.exist;
|
|
83
|
+
expect(dateColumn?.textContent?.trim()).to.equal('Jan 01, 2012');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should render added date when sorting by it', async () => {
|
|
87
|
+
const model: Partial<TileModel> = {
|
|
88
|
+
dateAdded: new Date('2010-01-01'),
|
|
89
|
+
dateArchived: new Date('2011-01-01'),
|
|
90
|
+
datePublished: new Date('2012-01-01'),
|
|
91
|
+
dateReviewed: new Date('2013-01-01'),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const el = await fixture<TileListCompact>(html`
|
|
95
|
+
<tile-list-compact
|
|
96
|
+
.model=${model}
|
|
97
|
+
.sortParam=${{ field: 'addeddate', direction: 'desc' }}
|
|
98
|
+
>
|
|
99
|
+
</tile-list-compact>
|
|
100
|
+
`);
|
|
101
|
+
|
|
102
|
+
const dateColumn = el.shadowRoot?.getElementById('date');
|
|
103
|
+
expect(dateColumn).to.exist;
|
|
104
|
+
expect(dateColumn?.textContent?.trim()).to.equal('Jan 01, 2010');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should render archived date when sorting by it', async () => {
|
|
108
|
+
const model: Partial<TileModel> = {
|
|
109
|
+
dateAdded: new Date('2010-01-01'),
|
|
110
|
+
dateArchived: new Date('2011-01-01'),
|
|
111
|
+
datePublished: new Date('2012-01-01'),
|
|
112
|
+
dateReviewed: new Date('2013-01-01'),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const el = await fixture<TileListCompact>(html`
|
|
116
|
+
<tile-list-compact
|
|
117
|
+
.model=${model}
|
|
118
|
+
.sortParam=${{ field: 'publicdate', direction: 'desc' }}
|
|
119
|
+
>
|
|
120
|
+
</tile-list-compact>
|
|
121
|
+
`);
|
|
122
|
+
|
|
123
|
+
const dateColumn = el.shadowRoot?.getElementById('date');
|
|
124
|
+
expect(dateColumn).to.exist;
|
|
125
|
+
expect(dateColumn?.textContent?.trim()).to.equal('Jan 01, 2011');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should render reviewed date when sorting by it', async () => {
|
|
129
|
+
const model: Partial<TileModel> = {
|
|
130
|
+
dateAdded: new Date('2010-01-01'),
|
|
131
|
+
dateArchived: new Date('2011-01-01'),
|
|
132
|
+
datePublished: new Date('2012-01-01'),
|
|
133
|
+
dateReviewed: new Date('2013-01-01'),
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const el = await fixture<TileListCompact>(html`
|
|
137
|
+
<tile-list-compact
|
|
138
|
+
.model=${model}
|
|
139
|
+
.sortParam=${{ field: 'reviewdate', direction: 'desc' }}
|
|
140
|
+
>
|
|
141
|
+
</tile-list-compact>
|
|
142
|
+
`);
|
|
143
|
+
|
|
144
|
+
const dateColumn = el.shadowRoot?.getElementById('date');
|
|
145
|
+
expect(dateColumn).to.exist;
|
|
146
|
+
expect(dateColumn?.textContent?.trim()).to.equal('Jan 01, 2013');
|
|
147
|
+
});
|
|
38
148
|
});
|