@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.
Files changed (62) hide show
  1. package/dist/src/app-root.d.ts +4 -0
  2. package/dist/src/app-root.js +99 -53
  3. package/dist/src/app-root.js.map +1 -1
  4. package/dist/src/collection-browser.d.ts +1 -2
  5. package/dist/src/collection-browser.js +21 -49
  6. package/dist/src/collection-browser.js.map +1 -1
  7. package/dist/src/collection-facets/facet-tombstone-row.d.ts +5 -0
  8. package/dist/src/collection-facets/facet-tombstone-row.js +43 -0
  9. package/dist/src/collection-facets/facet-tombstone-row.js.map +1 -0
  10. package/dist/src/collection-facets/facets-template.js +5 -3
  11. package/dist/src/collection-facets/facets-template.js.map +1 -1
  12. package/dist/src/collection-facets.d.ts +2 -0
  13. package/dist/src/collection-facets.js +44 -18
  14. package/dist/src/collection-facets.js.map +1 -1
  15. package/dist/src/models.d.ts +1 -0
  16. package/dist/src/models.js.map +1 -1
  17. package/dist/src/restoration-state-handler.d.ts +2 -1
  18. package/dist/src/restoration-state-handler.js +10 -0
  19. package/dist/src/restoration-state-handler.js.map +1 -1
  20. package/dist/src/tiles/grid/tile-stats.js +11 -5
  21. package/dist/src/tiles/grid/tile-stats.js.map +1 -1
  22. package/dist/src/tiles/list/tile-list-compact.d.ts +1 -0
  23. package/dist/src/tiles/list/tile-list-compact.js +8 -4
  24. package/dist/src/tiles/list/tile-list-compact.js.map +1 -1
  25. package/dist/src/tiles/list/tile-list.js +5 -2
  26. package/dist/src/tiles/list/tile-list.js.map +1 -1
  27. package/dist/src/tiles/mediatype-icon.js +2 -0
  28. package/dist/src/tiles/mediatype-icon.js.map +1 -1
  29. package/dist/test/collection-browser.test.js +125 -3
  30. package/dist/test/collection-browser.test.js.map +1 -1
  31. package/dist/test/collection-facets/facets-template.test.js +4 -4
  32. package/dist/test/collection-facets/facets-template.test.js.map +1 -1
  33. package/dist/test/mocks/mock-search-responses.d.ts +3 -0
  34. package/dist/test/mocks/mock-search-responses.js +95 -0
  35. package/dist/test/mocks/mock-search-responses.js.map +1 -1
  36. package/dist/test/mocks/mock-search-service.js +15 -8
  37. package/dist/test/mocks/mock-search-service.js.map +1 -1
  38. package/dist/test/restoration-state-handler.test.js +9 -0
  39. package/dist/test/restoration-state-handler.test.js.map +1 -1
  40. package/dist/test/tiles/list/tile-list-compact.test.js +99 -0
  41. package/dist/test/tiles/list/tile-list-compact.test.js.map +1 -1
  42. package/dist/test/tiles/list/tile-list.test.js +32 -0
  43. package/dist/test/tiles/list/tile-list.test.js.map +1 -1
  44. package/package.json +3 -3
  45. package/src/app-root.ts +104 -55
  46. package/src/collection-browser.ts +26 -48
  47. package/src/collection-facets/facet-tombstone-row.ts +40 -0
  48. package/src/collection-facets/facets-template.ts +5 -3
  49. package/src/collection-facets.ts +49 -18
  50. package/src/models.ts +1 -0
  51. package/src/restoration-state-handler.ts +19 -1
  52. package/src/tiles/grid/tile-stats.ts +18 -5
  53. package/src/tiles/list/tile-list-compact.ts +7 -3
  54. package/src/tiles/list/tile-list.ts +6 -1
  55. package/src/tiles/mediatype-icon.ts +2 -0
  56. package/test/collection-browser.test.ts +169 -3
  57. package/test/collection-facets/facets-template.test.ts +5 -3
  58. package/test/mocks/mock-search-responses.ts +107 -0
  59. package/test/mocks/mock-search-service.ts +16 -8
  60. package/test/restoration-state-handler.test.ts +12 -0
  61. package/test/tiles/list/tile-list-compact.test.ts +110 -0
  62. 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
- ${formatCount(this.favCount, 'short', 'short')}
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
- ${formatCount(this.commentCount, 'short', 'short')}
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(this.model?.viewCount ?? 0, this.formatSize)}`,
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></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></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></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 perticular facet group', async () => {
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(facetInfo?.querySelector('.facet-count')?.textContent).equal('1001');
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
- if (this.searchParams?.query === 'single-result') {
44
- return mockSuccessSingleResult;
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
  });