@internetarchive/collection-browser 0.3.5-alpha.1 → 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 (236) hide show
  1. package/.editorconfig +29 -29
  2. package/.github/workflows/ci.yml +26 -26
  3. package/.github/workflows/gh-pages-main.yml +39 -39
  4. package/.github/workflows/npm-publish.yml +39 -39
  5. package/.github/workflows/pr-preview.yml +38 -38
  6. package/.husky/pre-commit +4 -4
  7. package/LICENSE +661 -661
  8. package/README.md +83 -83
  9. package/dist/index.d.ts +9 -9
  10. package/dist/index.js +9 -9
  11. package/dist/src/app-root.d.ts +47 -43
  12. package/dist/src/app-root.js +331 -285
  13. package/dist/src/app-root.js.map +1 -1
  14. package/dist/src/assets/img/icons/arrow-left.d.ts +2 -2
  15. package/dist/src/assets/img/icons/arrow-left.js +2 -2
  16. package/dist/src/assets/img/icons/arrow-right.d.ts +2 -2
  17. package/dist/src/assets/img/icons/arrow-right.js +2 -2
  18. package/dist/src/assets/img/icons/chevron.d.ts +2 -2
  19. package/dist/src/assets/img/icons/chevron.js +2 -2
  20. package/dist/src/assets/img/icons/empty-query.d.ts +2 -2
  21. package/dist/src/assets/img/icons/empty-query.js +2 -2
  22. package/dist/src/assets/img/icons/eye-closed.d.ts +2 -2
  23. package/dist/src/assets/img/icons/eye-closed.js +2 -2
  24. package/dist/src/assets/img/icons/eye.d.ts +2 -2
  25. package/dist/src/assets/img/icons/eye.js +2 -2
  26. package/dist/src/assets/img/icons/favorite-filled.d.ts +1 -1
  27. package/dist/src/assets/img/icons/favorite-filled.js +2 -2
  28. package/dist/src/assets/img/icons/login-required.d.ts +1 -1
  29. package/dist/src/assets/img/icons/login-required.js +2 -2
  30. package/dist/src/assets/img/icons/mediatype/account.d.ts +1 -1
  31. package/dist/src/assets/img/icons/mediatype/account.js +2 -2
  32. package/dist/src/assets/img/icons/mediatype/audio.d.ts +1 -1
  33. package/dist/src/assets/img/icons/mediatype/audio.js +2 -2
  34. package/dist/src/assets/img/icons/mediatype/collection.d.ts +1 -1
  35. package/dist/src/assets/img/icons/mediatype/collection.js +2 -2
  36. package/dist/src/assets/img/icons/mediatype/data.d.ts +1 -1
  37. package/dist/src/assets/img/icons/mediatype/data.js +2 -2
  38. package/dist/src/assets/img/icons/mediatype/etree.d.ts +1 -1
  39. package/dist/src/assets/img/icons/mediatype/etree.js +2 -2
  40. package/dist/src/assets/img/icons/mediatype/film.d.ts +1 -1
  41. package/dist/src/assets/img/icons/mediatype/film.js +2 -2
  42. package/dist/src/assets/img/icons/mediatype/images.d.ts +1 -1
  43. package/dist/src/assets/img/icons/mediatype/images.js +2 -2
  44. package/dist/src/assets/img/icons/mediatype/radio.d.ts +1 -1
  45. package/dist/src/assets/img/icons/mediatype/radio.js +2 -2
  46. package/dist/src/assets/img/icons/mediatype/software.d.ts +1 -1
  47. package/dist/src/assets/img/icons/mediatype/software.js +2 -2
  48. package/dist/src/assets/img/icons/mediatype/texts.d.ts +1 -1
  49. package/dist/src/assets/img/icons/mediatype/texts.js +2 -2
  50. package/dist/src/assets/img/icons/mediatype/tv.d.ts +1 -1
  51. package/dist/src/assets/img/icons/mediatype/tv.js +2 -2
  52. package/dist/src/assets/img/icons/mediatype/video.d.ts +1 -1
  53. package/dist/src/assets/img/icons/mediatype/video.js +2 -2
  54. package/dist/src/assets/img/icons/mediatype/web.d.ts +1 -1
  55. package/dist/src/assets/img/icons/mediatype/web.js +2 -2
  56. package/dist/src/assets/img/icons/null-result.d.ts +2 -2
  57. package/dist/src/assets/img/icons/null-result.js +2 -2
  58. package/dist/src/assets/img/icons/restricted.d.ts +1 -1
  59. package/dist/src/assets/img/icons/restricted.js +2 -2
  60. package/dist/src/assets/img/icons/reviews.d.ts +1 -1
  61. package/dist/src/assets/img/icons/reviews.js +2 -2
  62. package/dist/src/assets/img/icons/upload.d.ts +1 -1
  63. package/dist/src/assets/img/icons/upload.js +2 -2
  64. package/dist/src/assets/img/icons/views.d.ts +1 -1
  65. package/dist/src/assets/img/icons/views.js +2 -2
  66. package/dist/src/circular-activity-indicator.d.ts +5 -5
  67. package/dist/src/circular-activity-indicator.js +17 -17
  68. package/dist/src/collection-browser.d.ts +246 -245
  69. package/dist/src/collection-browser.js +1370 -1359
  70. package/dist/src/collection-browser.js.map +1 -1
  71. package/dist/src/collection-facets/facet-tombstone-row.d.ts +5 -5
  72. package/dist/src/collection-facets/facet-tombstone-row.js +42 -42
  73. package/dist/src/collection-facets/facet-tombstone-row.js.map +1 -1
  74. package/dist/src/collection-facets/facets-template.d.ts +16 -16
  75. package/dist/src/collection-facets/facets-template.js +130 -128
  76. package/dist/src/collection-facets/facets-template.js.map +1 -1
  77. package/dist/src/collection-facets/more-facets-content.d.ts +76 -76
  78. package/dist/src/collection-facets/more-facets-content.js +353 -353
  79. package/dist/src/collection-facets/more-facets-pagination.d.ts +36 -36
  80. package/dist/src/collection-facets/more-facets-pagination.js +192 -192
  81. package/dist/src/collection-facets.d.ts +77 -77
  82. package/dist/src/collection-facets.js +551 -551
  83. package/dist/src/collection-facets.js.map +1 -1
  84. package/dist/src/empty-placeholder.d.ts +11 -11
  85. package/dist/src/empty-placeholder.js +42 -42
  86. package/dist/src/language-code-handler/language-code-handler.d.ts +37 -37
  87. package/dist/src/language-code-handler/language-code-handler.js +26 -26
  88. package/dist/src/language-code-handler/language-code-mapping.d.ts +1 -1
  89. package/dist/src/language-code-handler/language-code-mapping.js +562 -562
  90. package/dist/src/mediatype/mediatype-config.d.ts +3 -3
  91. package/dist/src/mediatype/mediatype-config.js +85 -85
  92. package/dist/src/models.d.ts +103 -102
  93. package/dist/src/models.js +117 -117
  94. package/dist/src/models.js.map +1 -1
  95. package/dist/src/restoration-state-handler.d.ts +46 -45
  96. package/dist/src/restoration-state-handler.js +230 -220
  97. package/dist/src/restoration-state-handler.js.map +1 -1
  98. package/dist/src/sort-filter-bar/alpha-bar.d.ts +9 -9
  99. package/dist/src/sort-filter-bar/alpha-bar.js +41 -41
  100. package/dist/src/sort-filter-bar/img/compact.d.ts +1 -1
  101. package/dist/src/sort-filter-bar/img/compact.js +2 -2
  102. package/dist/src/sort-filter-bar/img/list.d.ts +1 -1
  103. package/dist/src/sort-filter-bar/img/list.js +2 -2
  104. package/dist/src/sort-filter-bar/img/sort-triangle.d.ts +1 -1
  105. package/dist/src/sort-filter-bar/img/sort-triangle.js +2 -2
  106. package/dist/src/sort-filter-bar/img/tile.d.ts +1 -1
  107. package/dist/src/sort-filter-bar/img/tile.js +2 -2
  108. package/dist/src/sort-filter-bar/sort-filter-bar.d.ts +107 -107
  109. package/dist/src/sort-filter-bar/sort-filter-bar.js +423 -423
  110. package/dist/src/styles/item-image-styles.d.ts +8 -8
  111. package/dist/src/styles/item-image-styles.js +9 -9
  112. package/dist/src/tiles/collection-browser-loading-tile.d.ts +5 -5
  113. package/dist/src/tiles/collection-browser-loading-tile.js +15 -15
  114. package/dist/src/tiles/grid/account-tile.d.ts +8 -8
  115. package/dist/src/tiles/grid/account-tile.js +20 -20
  116. package/dist/src/tiles/grid/collection-tile.d.ts +7 -7
  117. package/dist/src/tiles/grid/collection-tile.js +23 -23
  118. package/dist/src/tiles/grid/item-tile.d.ts +24 -24
  119. package/dist/src/tiles/grid/item-tile.js +87 -87
  120. package/dist/src/tiles/grid/tile-stats.d.ts +10 -10
  121. package/dist/src/tiles/grid/tile-stats.js +46 -40
  122. package/dist/src/tiles/grid/tile-stats.js.map +1 -1
  123. package/dist/src/tiles/image-block.d.ts +17 -17
  124. package/dist/src/tiles/image-block.js +69 -69
  125. package/dist/src/tiles/item-image.d.ts +31 -31
  126. package/dist/src/tiles/item-image.js +103 -103
  127. package/dist/src/tiles/list/account-label.d.ts +1 -1
  128. package/dist/src/tiles/list/account-label.js +6 -6
  129. package/dist/src/tiles/list/date-label.d.ts +1 -1
  130. package/dist/src/tiles/list/date-label.js +12 -12
  131. package/dist/src/tiles/list/tile-list-compact-header.d.ts +12 -12
  132. package/dist/src/tiles/list/tile-list-compact-header.js +41 -41
  133. package/dist/src/tiles/list/tile-list-compact.d.ts +21 -20
  134. package/dist/src/tiles/list/tile-list-compact.js +94 -90
  135. package/dist/src/tiles/list/tile-list-compact.js.map +1 -1
  136. package/dist/src/tiles/list/tile-list.d.ts +50 -50
  137. package/dist/src/tiles/list/tile-list.js +271 -268
  138. package/dist/src/tiles/list/tile-list.js.map +1 -1
  139. package/dist/src/tiles/mediatype-icon.d.ts +9 -9
  140. package/dist/src/tiles/mediatype-icon.js +49 -47
  141. package/dist/src/tiles/mediatype-icon.js.map +1 -1
  142. package/dist/src/tiles/overlay/icon-overlay.d.ts +7 -7
  143. package/dist/src/tiles/overlay/icon-overlay.js +30 -30
  144. package/dist/src/tiles/overlay/text-overlay.d.ts +8 -8
  145. package/dist/src/tiles/overlay/text-overlay.js +31 -31
  146. package/dist/src/tiles/text-snippet-block.d.ts +29 -29
  147. package/dist/src/tiles/text-snippet-block.js +81 -81
  148. package/dist/src/tiles/tile-dispatcher.d.ts +36 -36
  149. package/dist/src/tiles/tile-dispatcher.js +128 -128
  150. package/dist/src/utils/analytics-events.d.ts +22 -22
  151. package/dist/src/utils/analytics-events.js +24 -24
  152. package/dist/src/utils/format-count.d.ts +7 -7
  153. package/dist/src/utils/format-count.js +76 -76
  154. package/dist/src/utils/format-date.d.ts +2 -2
  155. package/dist/src/utils/format-date.js +23 -23
  156. package/dist/test/collection-browser.test.d.ts +1 -1
  157. package/dist/test/collection-browser.test.js +481 -415
  158. package/dist/test/collection-browser.test.js.map +1 -1
  159. package/dist/test/collection-facets/facets-template.test.d.ts +1 -1
  160. package/dist/test/collection-facets/facets-template.test.js +62 -62
  161. package/dist/test/collection-facets/facets-template.test.js.map +1 -1
  162. package/dist/test/collection-facets/more-facets-content.test.d.ts +1 -1
  163. package/dist/test/collection-facets/more-facets-content.test.js +114 -114
  164. package/dist/test/collection-facets/more-facets-pagination.test.d.ts +1 -1
  165. package/dist/test/collection-facets/more-facets-pagination.test.js +117 -117
  166. package/dist/test/collection-facets.test.d.ts +2 -2
  167. package/dist/test/collection-facets.test.js +498 -498
  168. package/dist/test/empty-placeholder.test.d.ts +1 -1
  169. package/dist/test/empty-placeholder.test.js +33 -33
  170. package/dist/test/icon-overlay.test.d.ts +1 -1
  171. package/dist/test/icon-overlay.test.js +24 -24
  172. package/dist/test/image-block.test.d.ts +1 -1
  173. package/dist/test/image-block.test.js +48 -48
  174. package/dist/test/item-image.test.d.ts +1 -1
  175. package/dist/test/item-image.test.js +56 -56
  176. package/dist/test/mediatype-config.test.d.ts +1 -1
  177. package/dist/test/mediatype-config.test.js +16 -16
  178. package/dist/test/mocks/mock-analytics-handler.d.ts +10 -10
  179. package/dist/test/mocks/mock-analytics-handler.js +15 -15
  180. package/dist/test/mocks/mock-collection-name-cache.d.ts +7 -7
  181. package/dist/test/mocks/mock-collection-name-cache.js +13 -13
  182. package/dist/test/mocks/mock-search-responses.d.ts +8 -8
  183. package/dist/test/mocks/mock-search-responses.js +198 -198
  184. package/dist/test/mocks/mock-search-responses.js.map +1 -1
  185. package/dist/test/mocks/mock-search-service.d.ts +13 -13
  186. package/dist/test/mocks/mock-search-service.js +32 -32
  187. package/dist/test/mocks/mock-search-service.js.map +1 -1
  188. package/dist/test/restoration-state-handler.test.d.ts +1 -1
  189. package/dist/test/restoration-state-handler.test.js +126 -117
  190. package/dist/test/restoration-state-handler.test.js.map +1 -1
  191. package/dist/test/sort-filter-bar/sort-filter-bar.test.d.ts +1 -1
  192. package/dist/test/sort-filter-bar/sort-filter-bar.test.js +113 -113
  193. package/dist/test/text-overlay.test.d.ts +1 -1
  194. package/dist/test/text-overlay.test.js +41 -41
  195. package/dist/test/text-snippet-block.test.d.ts +1 -1
  196. package/dist/test/text-snippet-block.test.js +57 -57
  197. package/dist/test/tile-stats.test.d.ts +1 -1
  198. package/dist/test/tile-stats.test.js +33 -33
  199. package/dist/test/tiles/grid/item-tile.test.d.ts +1 -1
  200. package/dist/test/tiles/grid/item-tile.test.js +107 -107
  201. package/dist/test/tiles/list/tile-list-compact.test.d.ts +1 -1
  202. package/dist/test/tiles/list/tile-list-compact.test.js +125 -26
  203. package/dist/test/tiles/list/tile-list-compact.test.js.map +1 -1
  204. package/dist/test/tiles/list/tile-list.test.d.ts +1 -1
  205. package/dist/test/tiles/list/tile-list.test.js +79 -47
  206. package/dist/test/tiles/list/tile-list.test.js.map +1 -1
  207. package/dist/test/utils/format-count.test.d.ts +1 -1
  208. package/dist/test/utils/format-count.test.js +23 -23
  209. package/dist/test/utils/format-date.test.d.ts +1 -1
  210. package/dist/test/utils/format-date.test.js +17 -17
  211. package/index.html +24 -24
  212. package/local.archive.org.cert +86 -86
  213. package/local.archive.org.key +27 -27
  214. package/package.json +115 -115
  215. package/renovate.json +6 -6
  216. package/src/app-root.ts +104 -55
  217. package/src/collection-browser.ts +1503 -1488
  218. package/src/collection-facets/facet-tombstone-row.ts +40 -40
  219. package/src/collection-facets/facets-template.ts +5 -3
  220. package/src/collection-facets.ts +635 -635
  221. package/src/models.ts +1 -0
  222. package/src/restoration-state-handler.ts +19 -1
  223. package/src/tiles/grid/tile-stats.ts +18 -5
  224. package/src/tiles/list/tile-list-compact.ts +7 -3
  225. package/src/tiles/list/tile-list.ts +6 -1
  226. package/src/tiles/mediatype-icon.ts +2 -0
  227. package/test/collection-browser.test.ts +679 -599
  228. package/test/collection-facets/facets-template.test.ts +5 -3
  229. package/test/mocks/mock-search-responses.ts +226 -226
  230. package/test/mocks/mock-search-service.ts +61 -61
  231. package/test/restoration-state-handler.test.ts +12 -0
  232. package/test/tiles/list/tile-list-compact.test.ts +110 -0
  233. package/test/tiles/list/tile-list.test.ts +36 -0
  234. package/tsconfig.json +21 -21
  235. package/web-dev-server.config.mjs +30 -30
  236. package/web-test-runner.config.mjs +41 -41
@@ -1,599 +1,679 @@
1
- /* eslint-disable import/no-duplicates */
2
- import { expect, fixture } from '@open-wc/testing';
3
- import { html } from 'lit';
4
- import sinon from 'sinon';
5
- import type { InfiniteScroller } from '@internetarchive/infinite-scroller';
6
- import { SearchType } from '@internetarchive/search-service';
7
- import type { CollectionBrowser } from '../src/collection-browser';
8
- import '../src/collection-browser';
9
- import {
10
- defaultSelectedFacets,
11
- FacetBucket,
12
- SelectedFacets,
13
- SortField,
14
- } from '../src/models';
15
- import { MockSearchService } from './mocks/mock-search-service';
16
- import { MockCollectionNameCache } from './mocks/mock-collection-name-cache';
17
- import { MockAnalyticsHandler } from './mocks/mock-analytics-handler';
18
- import { analyticsCategories } from '../src/utils/analytics-events';
19
- import type { TileDispatcher } from '../src/tiles/tile-dispatcher';
20
-
21
- describe('Collection Browser', () => {
22
- it('clear existing filter for facets & sort-bar', async () => {
23
- const el = await fixture<CollectionBrowser>(
24
- html`<collection-browser></collection-browser>`
25
- );
26
-
27
- el.selectedSort = 'title' as SortField;
28
- await el.updateComplete;
29
- el.clearFilters();
30
-
31
- expect(el.selectedFacets).to.equal(defaultSelectedFacets);
32
- expect(el.selectedSort).to.equal('relevance');
33
- expect(el.sortDirection).to.null;
34
- expect(el.sortParam).to.null;
35
- expect(el.selectedCreatorFilter).to.null;
36
- expect(el.selectedTitleFilter).to.null;
37
- });
38
-
39
- it('filterBy creator with analytics', async () => {
40
- const mockAnalyticsHandler = new MockAnalyticsHandler();
41
- const el = await fixture<CollectionBrowser>(
42
- html`<collection-browser .analyticsHandler=${mockAnalyticsHandler}>
43
- </collection-browser>`
44
- );
45
-
46
- el.searchContext = 'betaSearchService';
47
- el.selectedCreatorFilter = 'A';
48
- await el.updateComplete;
49
-
50
- expect(mockAnalyticsHandler.callCategory).to.equal('betaSearchService');
51
- expect(mockAnalyticsHandler.callAction).to.equal('filterByCreator');
52
- expect(mockAnalyticsHandler.callLabel).to.equal('start-A');
53
-
54
- el.clearFilters();
55
- await el.updateComplete;
56
-
57
- expect(el.selectedTitleFilter).to.null;
58
- expect(mockAnalyticsHandler.callCategory).to.equal('betaSearchService');
59
- expect(mockAnalyticsHandler.callAction).to.equal('filterByCreator');
60
- expect(mockAnalyticsHandler.callLabel).to.equal('clear-A');
61
- });
62
-
63
- it('filterBy title with analytics', async () => {
64
- const mockAnalyticsHandler = new MockAnalyticsHandler();
65
- const el = await fixture<CollectionBrowser>(
66
- html`<collection-browser .analyticsHandler=${mockAnalyticsHandler}>
67
- </collection-browser>`
68
- );
69
-
70
- el.searchContext = 'beta-search-service';
71
- el.selectedSort = 'title' as SortField;
72
- el.selectedTitleFilter = 'A';
73
- await el.updateComplete;
74
-
75
- expect(mockAnalyticsHandler.callCategory).to.equal('beta-search-service');
76
- expect(mockAnalyticsHandler.callAction).to.equal('filterByTitle');
77
- expect(mockAnalyticsHandler.callLabel).to.equal('start-A');
78
-
79
- el.clearFilters();
80
- await el.updateComplete;
81
-
82
- expect(el.selectedTitleFilter).to.null;
83
- expect(mockAnalyticsHandler.callCategory).to.equal('beta-search-service');
84
- expect(mockAnalyticsHandler.callAction).to.equal('filterByTitle');
85
- expect(mockAnalyticsHandler.callLabel).to.equal('clear-A');
86
- });
87
-
88
- it('selected facets with analytics - not negative facets', async () => {
89
- const mockAnalyticsHandler = new MockAnalyticsHandler();
90
- const mediaTypeBucket = { count: 123, state: 'selected' } as FacetBucket;
91
- const mockedSelectedFacets: SelectedFacets = {
92
- subject: {},
93
- lending: {},
94
- mediatype: { data: mediaTypeBucket },
95
- language: {},
96
- creator: {},
97
- collection: {},
98
- year: {},
99
- };
100
-
101
- const el = await fixture<CollectionBrowser>(
102
- html`<collection-browser .analyticsHandler=${mockAnalyticsHandler}>
103
- </collection-browser>`
104
- );
105
-
106
- el.searchContext = 'search-service';
107
- el.selectedFacets = mockedSelectedFacets;
108
- await el.updateComplete;
109
-
110
- el.facetClickHandler('mediatype', true, false);
111
- expect(mockAnalyticsHandler.callCategory).to.equal('search-service');
112
- expect(mockAnalyticsHandler.callAction).to.equal('facetSelected');
113
- expect(mockAnalyticsHandler.callLabel).to.equal('mediatype');
114
-
115
- el.facetClickHandler('mediatype', false, false);
116
- expect(el.selectedFacets).to.equal(mockedSelectedFacets);
117
- expect(mockAnalyticsHandler.callCategory).to.equal('search-service');
118
- expect(mockAnalyticsHandler.callAction).to.equal('facetDeselected');
119
- expect(mockAnalyticsHandler.callLabel).to.equal('mediatype');
120
- });
121
-
122
- it('selected facets with analytics - negative facets', async () => {
123
- const mockAnalyticsHandler = new MockAnalyticsHandler();
124
- const mediaTypeBucket = { count: 123, state: 'selected' } as FacetBucket;
125
- const mockedSelectedFacets: SelectedFacets = {
126
- subject: {},
127
- lending: {},
128
- mediatype: { data: mediaTypeBucket },
129
- language: {},
130
- creator: {},
131
- collection: {},
132
- year: {},
133
- };
134
-
135
- const el = await fixture<CollectionBrowser>(
136
- html`<collection-browser .analyticsHandler=${mockAnalyticsHandler}>
137
- </collection-browser>`
138
- );
139
-
140
- el.searchContext = 'beta-search-service';
141
- el.selectedFacets = mockedSelectedFacets;
142
- await el.updateComplete;
143
-
144
- el.facetClickHandler('mediatype', true, true);
145
- expect(mockAnalyticsHandler.callCategory).to.equal('beta-search-service');
146
- expect(mockAnalyticsHandler.callAction).to.equal('facetNegativeSelected');
147
- expect(mockAnalyticsHandler.callLabel).to.equal('mediatype');
148
-
149
- el.facetClickHandler('mediatype', false, true);
150
- expect(el.selectedFacets).to.equal(mockedSelectedFacets);
151
- expect(mockAnalyticsHandler.callCategory).to.equal('beta-search-service');
152
- expect(mockAnalyticsHandler.callAction).to.equal('facetNegativeDeselected');
153
- expect(mockAnalyticsHandler.callLabel).to.equal('mediatype');
154
- });
155
-
156
- it('should render with a sort bar, facets, and infinite scroller', async () => {
157
- const searchService = new MockSearchService();
158
-
159
- const el = await fixture<CollectionBrowser>(
160
- html`<collection-browser .searchService=${searchService}>
161
- </collection-browser>`
162
- );
163
-
164
- el.baseQuery = 'hello';
165
- await el.updateComplete;
166
-
167
- const facets = el.shadowRoot?.querySelector('collection-facets');
168
- const sortBar = el.shadowRoot?.querySelector('sort-filter-bar');
169
- const infiniteScroller = el.shadowRoot?.querySelector('infinite-scroller');
170
- expect(facets).to.exist;
171
- expect(sortBar).to.exist;
172
- expect(infiniteScroller).to.exist;
173
- });
174
-
175
- it('queries the search service when given a base query', async () => {
176
- const searchService = new MockSearchService();
177
-
178
- const el = await fixture<CollectionBrowser>(
179
- html`<collection-browser .searchService=${searchService}>
180
- </collection-browser>`
181
- );
182
-
183
- el.baseQuery = 'collection:foo';
184
- await el.updateComplete;
185
-
186
- expect(searchService.searchParams?.query).to.equal('collection:foo');
187
- expect(
188
- el.shadowRoot?.querySelector('#big-results-label')?.textContent
189
- ).to.contains('Results');
190
- });
191
-
192
- it('queries the search service with a metadata search', async () => {
193
- const searchService = new MockSearchService();
194
-
195
- const el = await fixture<CollectionBrowser>(
196
- html` <collection-browser
197
- .searchService=${searchService}
198
- .searchType=${SearchType.METADATA}
199
- >
200
- </collection-browser>`
201
- );
202
-
203
- el.baseQuery = 'collection:foo';
204
- await el.updateComplete;
205
-
206
- expect(searchService.searchParams?.query).to.equal('collection:foo');
207
- expect(searchService.searchType).to.equal(SearchType.METADATA);
208
- expect(
209
- el.shadowRoot?.querySelector('#big-results-label')?.textContent
210
- ).to.contains('Results');
211
- });
212
-
213
- it('queries the search service with a fulltext search', async () => {
214
- const searchService = new MockSearchService();
215
-
216
- const el = await fixture<CollectionBrowser>(
217
- html` <collection-browser
218
- .searchService=${searchService}
219
- .searchType=${SearchType.FULLTEXT}
220
- >
221
- </collection-browser>`
222
- );
223
-
224
- el.baseQuery = 'collection:foo';
225
- await el.updateComplete;
226
-
227
- expect(searchService.searchParams?.query).to.equal('collection:foo');
228
- expect(searchService.searchType).to.equal(SearchType.FULLTEXT);
229
- expect(
230
- el.shadowRoot?.querySelector('#big-results-label')?.textContent
231
- ).to.contains('Results');
232
- });
233
-
234
- it('fails gracefully if no search service provided', async () => {
235
- const el = await fixture<CollectionBrowser>(
236
- html`<collection-browser></collection-browser>`
237
- );
238
-
239
- el.baseQuery = 'collection:foo';
240
- await el.updateComplete;
241
-
242
- // This shouldn't throw an error
243
- expect(el.fetchPage(2)).to.exist;
244
-
245
- // Should continue showing the empty placeholder
246
- expect(el.shadowRoot?.querySelector('empty-placeholder')).to.exist;
247
- });
248
-
249
- it('applies loggedin flag to tile models if needed', async () => {
250
- const searchService = new MockSearchService();
251
-
252
- const el = await fixture<CollectionBrowser>(
253
- html`<collection-browser .searchService=${searchService}>
254
- </collection-browser>`
255
- );
256
-
257
- el.baseQuery = 'loggedin';
258
- await el.updateComplete;
259
-
260
- const cellTemplate = el.cellForIndex(0);
261
- expect(cellTemplate).to.exist;
262
-
263
- const cell = await fixture<TileDispatcher>(cellTemplate!);
264
- expect(cell).to.exist;
265
-
266
- expect(cell.model?.loginRequired).to.be.true;
267
- });
268
-
269
- it('applies no-preview flag to tile models if needed', async () => {
270
- const searchService = new MockSearchService();
271
-
272
- const el = await fixture<CollectionBrowser>(
273
- html`<collection-browser .searchService=${searchService}>
274
- </collection-browser>`
275
- );
276
-
277
- el.baseQuery = 'no-preview';
278
- await el.updateComplete;
279
-
280
- const cellTemplate = el.cellForIndex(0);
281
- expect(cellTemplate).to.exist;
282
-
283
- const cell = await fixture<TileDispatcher>(cellTemplate!);
284
- expect(cell).to.exist;
285
-
286
- expect(cell.model?.contentWarning).to.be.true;
287
- });
288
-
289
- it('both loggedin and no-preview flags can be set simultaneously', async () => {
290
- const searchService = new MockSearchService();
291
-
292
- const el = await fixture<CollectionBrowser>(
293
- html`<collection-browser .searchService=${searchService}>
294
- </collection-browser>`
295
- );
296
-
297
- el.baseQuery = 'loggedin-no-preview';
298
- await el.updateComplete;
299
-
300
- const cellTemplate = el.cellForIndex(0);
301
- expect(cellTemplate).to.exist;
302
-
303
- const cell = await fixture<TileDispatcher>(cellTemplate!);
304
- expect(cell).to.exist;
305
-
306
- expect(cell.model?.loginRequired).to.be.true;
307
- expect(cell.model?.contentWarning).to.be.true;
308
- });
309
-
310
- it('can search on demand if only search type has changed', async () => {
311
- const searchService = new MockSearchService();
312
-
313
- const el = await fixture<CollectionBrowser>(
314
- html`<collection-browser
315
- .searchService=${searchService}
316
- .searchType=${SearchType.METADATA}
317
- ></collection-browser>`
318
- );
319
-
320
- el.baseQuery = 'collection:foo';
321
- await el.updateComplete;
322
-
323
- el.searchType = SearchType.FULLTEXT;
324
- await el.updateComplete;
325
-
326
- // Haven't performed the search yet
327
- expect(searchService.searchType).to.equal(SearchType.METADATA);
328
-
329
- el.requestSearch();
330
- expect(searchService.searchType).to.equal(SearchType.FULLTEXT);
331
- });
332
-
333
- it('queries for collection names after a fetch', async () => {
334
- const searchService = new MockSearchService();
335
- const collectionNameCache = new MockCollectionNameCache();
336
-
337
- const el = await fixture<CollectionBrowser>(
338
- html`<collection-browser
339
- .searchService=${searchService}
340
- .collectionNameCache=${collectionNameCache}
341
- >
342
- </collection-browser>`
343
- );
344
-
345
- el.baseQuery = 'collection:foo';
346
- await el.updateComplete;
347
-
348
- expect(collectionNameCache.preloadIdentifiersRequested).to.deep.equal([
349
- 'foo',
350
- 'bar',
351
- 'baz',
352
- 'boop',
353
- ]);
354
- });
355
-
356
- it('keeps search results from fetch if no change to query or sort param', async () => {
357
- const resultsSpy = sinon.spy();
358
- const searchService = new MockSearchService({
359
- asyncResponse: true,
360
- resultsSpy,
361
- });
362
-
363
- const el = await fixture<CollectionBrowser>(
364
- html`<collection-browser .searchService=${searchService}>
365
- </collection-browser>`
366
- );
367
-
368
- el.baseQuery = 'with-sort';
369
- el.sortParam = { field: 'foo', direction: 'asc' };
370
- await el.updateComplete;
371
-
372
- await el.fetchPage(2);
373
-
374
- // If there is no change to the query or sort param during the fetch, the results
375
- // should be read.
376
- expect(resultsSpy.callCount).to.be.greaterThanOrEqual(1);
377
- });
378
-
379
- it('discards obsolete search results if sort params changed before arrival', async () => {
380
- const resultsSpy = sinon.spy();
381
- const searchService = new MockSearchService({
382
- asyncResponse: true,
383
- resultsSpy,
384
- });
385
-
386
- const el = await fixture<CollectionBrowser>(
387
- html`<collection-browser .searchService=${searchService}>
388
- </collection-browser>`
389
- );
390
-
391
- el.baseQuery = 'with-sort';
392
- el.sortParam = { field: 'foo', direction: 'asc' };
393
- await el.updateComplete;
394
-
395
- const fetchPromise = el.fetchPage(2);
396
- el.sortParam = { field: 'foo', direction: 'desc' };
397
- await fetchPromise;
398
-
399
- // If the different sort param causes the results to be discarded,
400
- // the results array should never be read.
401
- expect(resultsSpy.callCount).to.equal(0);
402
- });
403
-
404
- it('discards obsolete search results if sort param added before arrival', async () => {
405
- const resultsSpy = sinon.spy();
406
- const searchService = new MockSearchService({
407
- asyncResponse: true,
408
- resultsSpy,
409
- });
410
-
411
- const el = await fixture<CollectionBrowser>(
412
- html`<collection-browser .searchService=${searchService}>
413
- </collection-browser>`
414
- );
415
-
416
- el.baseQuery = 'single-result';
417
- await el.updateComplete;
418
-
419
- const fetchPromise = el.fetchPage(2);
420
- el.sortParam = { field: 'foo', direction: 'asc' };
421
- await fetchPromise;
422
-
423
- // If the different sort param causes the results to be discarded,
424
- // the results array should never be read.
425
- expect(resultsSpy.callCount).to.equal(0);
426
- });
427
-
428
- it('discards obsolete search results if sort param cleared before arrival', async () => {
429
- const resultsSpy = sinon.spy();
430
- const searchService = new MockSearchService({
431
- asyncResponse: true,
432
- resultsSpy,
433
- });
434
-
435
- const el = await fixture<CollectionBrowser>(
436
- html`<collection-browser .searchService=${searchService}>
437
- </collection-browser>`
438
- );
439
-
440
- el.baseQuery = 'with-sort';
441
- await el.updateComplete;
442
-
443
- const fetchPromise = el.fetchPage(2);
444
- el.sortParam = null;
445
- await fetchPromise;
446
-
447
- // If the different sort param causes the results to be discarded,
448
- // the results array should never be read.
449
- expect(resultsSpy.callCount).to.equal(0);
450
- });
451
-
452
- it('sets sort properties when user changes sort', async () => {
453
- const searchService = new MockSearchService();
454
-
455
- const el = await fixture<CollectionBrowser>(
456
- html`<collection-browser .searchService=${searchService}>
457
- </collection-browser>`
458
- );
459
-
460
- expect(el.selectedSort).to.equal(SortField.relevance);
461
-
462
- el.baseQuery = 'foo';
463
- await el.updateComplete;
464
-
465
- const sortBar = el.shadowRoot?.querySelector('sort-filter-bar');
466
- const sortSelector = sortBar?.shadowRoot?.querySelector(
467
- '#desktop-sort-selector'
468
- );
469
- expect(sortSelector).to.exist;
470
-
471
- // Click the title sorter
472
- [...(sortSelector?.children as HTMLCollection & Iterable<any>)] // tsc doesn't know children is iterable
473
- .find(child => child.textContent?.trim() === 'Title')
474
- ?.querySelector('a[href]')
475
- ?.click();
476
-
477
- await el.updateComplete;
478
-
479
- expect(el.selectedSort).to.equal(SortField.title);
480
- });
481
-
482
- it('scrolls to page', async () => {
483
- const searchService = new MockSearchService();
484
-
485
- const el = await fixture<CollectionBrowser>(
486
- html`<collection-browser .searchService=${searchService}>
487
- </collection-browser>`
488
- );
489
-
490
- const infiniteScroller = el.shadowRoot?.querySelector(
491
- 'infinite-scroller'
492
- ) as InfiniteScroller;
493
- expect(infiniteScroller).to.exist;
494
-
495
- const oldScrollToCell = infiniteScroller.scrollToCell;
496
- const spy = sinon.spy();
497
- infiniteScroller.scrollToCell = spy;
498
-
499
- el.goToPage(1);
500
-
501
- // Give it a second to scroll
502
- await new Promise(res => {
503
- setTimeout(res, 1000);
504
- });
505
-
506
- expect(spy.callCount).to.equal(1);
507
-
508
- infiniteScroller.scrollToCell = oldScrollToCell;
509
- });
510
-
511
- it('refreshes when certain properties change - with some analytics event sampling', async () => {
512
- const mockAnalyticsHandler = new MockAnalyticsHandler();
513
- const searchService = new MockSearchService();
514
- const collectionNameCache = new MockCollectionNameCache();
515
- const el = await fixture<CollectionBrowser>(
516
- html`<collection-browser
517
- .analyticsHandler=${mockAnalyticsHandler}
518
- .searchService=${searchService}
519
- .collectionNameCache=${collectionNameCache}
520
- ></collection-browser>`
521
- );
522
- const infiniteScrollerRefreshSpy = sinon.spy();
523
-
524
- const infiniteScroller = el.shadowRoot?.querySelector('infinite-scroller');
525
- (infiniteScroller as InfiniteScroller).reload = infiniteScrollerRefreshSpy;
526
- expect(infiniteScrollerRefreshSpy.called).to.be.false;
527
- expect(infiniteScrollerRefreshSpy.callCount).to.equal(0);
528
-
529
- // testing: `loggedIn`
530
- el.loggedIn = true;
531
- await el.updateComplete;
532
- expect(infiniteScrollerRefreshSpy.called).to.be.true;
533
- expect(infiniteScrollerRefreshSpy.callCount).to.equal(1);
534
-
535
- el.loggedIn = false;
536
- await el.updateComplete;
537
- expect(infiniteScrollerRefreshSpy.callCount).to.equal(2);
538
-
539
- // testing: `displayMode`
540
- el.displayMode = 'list-compact';
541
- el.searchContext = 'beta-search';
542
- await el.updateComplete;
543
- expect(infiniteScrollerRefreshSpy.callCount).to.equal(3);
544
-
545
- expect(mockAnalyticsHandler.callCategory).to.equal('beta-search');
546
- expect(mockAnalyticsHandler.callAction).to.equal('displayMode');
547
- expect(mockAnalyticsHandler.callLabel).to.equal('list-compact');
548
-
549
- el.displayMode = 'list-detail';
550
- await el.updateComplete;
551
- expect(infiniteScrollerRefreshSpy.callCount).to.equal(4);
552
-
553
- expect(mockAnalyticsHandler.callCategory).to.equal('beta-search');
554
- expect(mockAnalyticsHandler.callAction).to.equal('displayMode');
555
- expect(mockAnalyticsHandler.callLabel).to.equal('list-detail');
556
-
557
- // testing: `baseNavigationUrl`
558
- el.baseNavigationUrl = 'https://funtestsite.com';
559
- await el.updateComplete;
560
- expect(infiniteScrollerRefreshSpy.callCount).to.equal(5);
561
-
562
- // testing: `baseImageUrl`
563
- el.baseImageUrl = 'https://funtestsiteforimages.com';
564
- await el.updateComplete;
565
- expect(infiniteScrollerRefreshSpy.callCount).to.equal(6);
566
- });
567
-
568
- it('query the search service for single result', async () => {
569
- const searchService = new MockSearchService();
570
-
571
- const el = await fixture<CollectionBrowser>(
572
- html`<collection-browser .searchService=${searchService}>
573
- </collection-browser>`
574
- );
575
-
576
- el.baseQuery = 'single-result';
577
- await el.updateComplete;
578
-
579
- expect(
580
- el.shadowRoot?.querySelector('#big-results-label')?.textContent
581
- ).to.contains('Result');
582
- });
583
-
584
- it('`searchContext` prop helps describe where component is being used', async () => {
585
- const el = await fixture<CollectionBrowser>(
586
- html`<collection-browser></collection-browser>`
587
- );
588
-
589
- expect(el.searchContext).to.equal(analyticsCategories.default);
590
-
591
- el.searchContext = 'unicorn-search';
592
- await el.updateComplete;
593
-
594
- expect(el.searchContext).to.equal('unicorn-search');
595
-
596
- // property is reflected as attribute
597
- expect(el.getAttribute('searchcontext')).to.equal('unicorn-search');
598
- });
599
- });
1
+ /* eslint-disable import/no-duplicates */
2
+ import { expect, fixture } from '@open-wc/testing';
3
+ import { html } from 'lit';
4
+ import sinon from 'sinon';
5
+ import type { InfiniteScroller } from '@internetarchive/infinite-scroller';
6
+ import { SearchType } from '@internetarchive/search-service';
7
+ import type { CollectionBrowser } from '../src/collection-browser';
8
+ import '../src/collection-browser';
9
+ import {
10
+ defaultSelectedFacets,
11
+ FacetBucket,
12
+ SelectedFacets,
13
+ SortField,
14
+ } from '../src/models';
15
+ import { MockSearchService } from './mocks/mock-search-service';
16
+ import { MockCollectionNameCache } from './mocks/mock-collection-name-cache';
17
+ import { MockAnalyticsHandler } from './mocks/mock-analytics-handler';
18
+ import { analyticsCategories } from '../src/utils/analytics-events';
19
+ import type { TileDispatcher } from '../src/tiles/tile-dispatcher';
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
+
37
+ it('clear existing filter for facets & sort-bar', async () => {
38
+ const el = await fixture<CollectionBrowser>(
39
+ html`<collection-browser></collection-browser>`
40
+ );
41
+
42
+ el.selectedSort = 'title' as SortField;
43
+ await el.updateComplete;
44
+ el.clearFilters();
45
+
46
+ expect(el.selectedFacets).to.equal(defaultSelectedFacets);
47
+ expect(el.selectedSort).to.equal('relevance');
48
+ expect(el.sortDirection).to.null;
49
+ expect(el.sortParam).to.null;
50
+ expect(el.selectedCreatorFilter).to.null;
51
+ expect(el.selectedTitleFilter).to.null;
52
+ });
53
+
54
+ it('filterBy creator with analytics', async () => {
55
+ const mockAnalyticsHandler = new MockAnalyticsHandler();
56
+ const el = await fixture<CollectionBrowser>(
57
+ html`<collection-browser .analyticsHandler=${mockAnalyticsHandler}>
58
+ </collection-browser>`
59
+ );
60
+
61
+ el.searchContext = 'betaSearchService';
62
+ el.selectedCreatorFilter = 'A';
63
+ await el.updateComplete;
64
+
65
+ expect(mockAnalyticsHandler.callCategory).to.equal('betaSearchService');
66
+ expect(mockAnalyticsHandler.callAction).to.equal('filterByCreator');
67
+ expect(mockAnalyticsHandler.callLabel).to.equal('start-A');
68
+
69
+ el.clearFilters();
70
+ await el.updateComplete;
71
+
72
+ expect(el.selectedTitleFilter).to.null;
73
+ expect(mockAnalyticsHandler.callCategory).to.equal('betaSearchService');
74
+ expect(mockAnalyticsHandler.callAction).to.equal('filterByCreator');
75
+ expect(mockAnalyticsHandler.callLabel).to.equal('clear-A');
76
+ });
77
+
78
+ it('filterBy title with analytics', async () => {
79
+ const mockAnalyticsHandler = new MockAnalyticsHandler();
80
+ const el = await fixture<CollectionBrowser>(
81
+ html`<collection-browser .analyticsHandler=${mockAnalyticsHandler}>
82
+ </collection-browser>`
83
+ );
84
+
85
+ el.searchContext = 'beta-search-service';
86
+ el.selectedSort = 'title' as SortField;
87
+ el.selectedTitleFilter = 'A';
88
+ await el.updateComplete;
89
+
90
+ expect(mockAnalyticsHandler.callCategory).to.equal('beta-search-service');
91
+ expect(mockAnalyticsHandler.callAction).to.equal('filterByTitle');
92
+ expect(mockAnalyticsHandler.callLabel).to.equal('start-A');
93
+
94
+ el.clearFilters();
95
+ await el.updateComplete;
96
+
97
+ expect(el.selectedTitleFilter).to.null;
98
+ expect(mockAnalyticsHandler.callCategory).to.equal('beta-search-service');
99
+ expect(mockAnalyticsHandler.callAction).to.equal('filterByTitle');
100
+ expect(mockAnalyticsHandler.callLabel).to.equal('clear-A');
101
+ });
102
+
103
+ it('selected facets with analytics - not negative facets', async () => {
104
+ const mockAnalyticsHandler = new MockAnalyticsHandler();
105
+ const mediaTypeBucket = { count: 123, state: 'selected' } as FacetBucket;
106
+ const mockedSelectedFacets: SelectedFacets = {
107
+ subject: {},
108
+ lending: {},
109
+ mediatype: { data: mediaTypeBucket },
110
+ language: {},
111
+ creator: {},
112
+ collection: {},
113
+ year: {},
114
+ };
115
+
116
+ const el = await fixture<CollectionBrowser>(
117
+ html`<collection-browser .analyticsHandler=${mockAnalyticsHandler}>
118
+ </collection-browser>`
119
+ );
120
+
121
+ el.searchContext = 'search-service';
122
+ el.selectedFacets = mockedSelectedFacets;
123
+ await el.updateComplete;
124
+
125
+ el.facetClickHandler('mediatype', true, false);
126
+ expect(mockAnalyticsHandler.callCategory).to.equal('search-service');
127
+ expect(mockAnalyticsHandler.callAction).to.equal('facetSelected');
128
+ expect(mockAnalyticsHandler.callLabel).to.equal('mediatype');
129
+
130
+ el.facetClickHandler('mediatype', false, false);
131
+ expect(el.selectedFacets).to.equal(mockedSelectedFacets);
132
+ expect(mockAnalyticsHandler.callCategory).to.equal('search-service');
133
+ expect(mockAnalyticsHandler.callAction).to.equal('facetDeselected');
134
+ expect(mockAnalyticsHandler.callLabel).to.equal('mediatype');
135
+ });
136
+
137
+ it('selected facets with analytics - negative facets', async () => {
138
+ const mockAnalyticsHandler = new MockAnalyticsHandler();
139
+ const mediaTypeBucket = { count: 123, state: 'selected' } as FacetBucket;
140
+ const mockedSelectedFacets: SelectedFacets = {
141
+ subject: {},
142
+ lending: {},
143
+ mediatype: { data: mediaTypeBucket },
144
+ language: {},
145
+ creator: {},
146
+ collection: {},
147
+ year: {},
148
+ };
149
+
150
+ const el = await fixture<CollectionBrowser>(
151
+ html`<collection-browser .analyticsHandler=${mockAnalyticsHandler}>
152
+ </collection-browser>`
153
+ );
154
+
155
+ el.searchContext = 'beta-search-service';
156
+ el.selectedFacets = mockedSelectedFacets;
157
+ await el.updateComplete;
158
+
159
+ el.facetClickHandler('mediatype', true, true);
160
+ expect(mockAnalyticsHandler.callCategory).to.equal('beta-search-service');
161
+ expect(mockAnalyticsHandler.callAction).to.equal('facetNegativeSelected');
162
+ expect(mockAnalyticsHandler.callLabel).to.equal('mediatype');
163
+
164
+ el.facetClickHandler('mediatype', false, true);
165
+ expect(el.selectedFacets).to.equal(mockedSelectedFacets);
166
+ expect(mockAnalyticsHandler.callCategory).to.equal('beta-search-service');
167
+ expect(mockAnalyticsHandler.callAction).to.equal('facetNegativeDeselected');
168
+ expect(mockAnalyticsHandler.callLabel).to.equal('mediatype');
169
+ });
170
+
171
+ it('should render with a sort bar, facets, and infinite scroller', async () => {
172
+ const searchService = new MockSearchService();
173
+
174
+ const el = await fixture<CollectionBrowser>(
175
+ html`<collection-browser .searchService=${searchService}>
176
+ </collection-browser>`
177
+ );
178
+
179
+ el.baseQuery = 'hello';
180
+ await el.updateComplete;
181
+
182
+ const facets = el.shadowRoot?.querySelector('collection-facets');
183
+ const sortBar = el.shadowRoot?.querySelector('sort-filter-bar');
184
+ const infiniteScroller = el.shadowRoot?.querySelector('infinite-scroller');
185
+ expect(facets).to.exist;
186
+ expect(sortBar).to.exist;
187
+ expect(infiniteScroller).to.exist;
188
+ });
189
+
190
+ it('queries the search service when given a base query', async () => {
191
+ const searchService = new MockSearchService();
192
+
193
+ const el = await fixture<CollectionBrowser>(
194
+ html`<collection-browser .searchService=${searchService}>
195
+ </collection-browser>`
196
+ );
197
+
198
+ el.baseQuery = 'collection:foo';
199
+ await el.updateComplete;
200
+
201
+ expect(searchService.searchParams?.query).to.equal('collection:foo');
202
+ expect(
203
+ el.shadowRoot?.querySelector('#big-results-label')?.textContent
204
+ ).to.contains('Results');
205
+ });
206
+
207
+ it('queries the search service with a metadata search', async () => {
208
+ const searchService = new MockSearchService();
209
+
210
+ const el = await fixture<CollectionBrowser>(
211
+ html` <collection-browser
212
+ .searchService=${searchService}
213
+ .searchType=${SearchType.METADATA}
214
+ >
215
+ </collection-browser>`
216
+ );
217
+
218
+ el.baseQuery = 'collection:foo';
219
+ await el.updateComplete;
220
+
221
+ expect(searchService.searchParams?.query).to.equal('collection:foo');
222
+ expect(searchService.searchType).to.equal(SearchType.METADATA);
223
+ expect(
224
+ el.shadowRoot?.querySelector('#big-results-label')?.textContent
225
+ ).to.contains('Results');
226
+ });
227
+
228
+ it('queries the search service with a fulltext search', async () => {
229
+ const searchService = new MockSearchService();
230
+
231
+ const el = await fixture<CollectionBrowser>(
232
+ html` <collection-browser
233
+ .searchService=${searchService}
234
+ .searchType=${SearchType.FULLTEXT}
235
+ >
236
+ </collection-browser>`
237
+ );
238
+
239
+ el.baseQuery = 'collection:foo';
240
+ await el.updateComplete;
241
+
242
+ expect(searchService.searchParams?.query).to.equal('collection:foo');
243
+ expect(searchService.searchType).to.equal(SearchType.FULLTEXT);
244
+ expect(
245
+ el.shadowRoot?.querySelector('#big-results-label')?.textContent
246
+ ).to.contains('Results');
247
+ });
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
+
384
+ it('can search on demand if only search type has changed', async () => {
385
+ const searchService = new MockSearchService();
386
+
387
+ const el = await fixture<CollectionBrowser>(
388
+ html`<collection-browser
389
+ .searchService=${searchService}
390
+ .searchType=${SearchType.METADATA}
391
+ ></collection-browser>`
392
+ );
393
+
394
+ el.baseQuery = 'collection:foo';
395
+ await el.updateComplete;
396
+
397
+ el.searchType = SearchType.FULLTEXT;
398
+ await el.updateComplete;
399
+
400
+ // Haven't performed the search yet
401
+ expect(searchService.searchType).to.equal(SearchType.METADATA);
402
+
403
+ el.requestSearch();
404
+ expect(searchService.searchType).to.equal(SearchType.FULLTEXT);
405
+ });
406
+
407
+ it('queries for collection names after a fetch', async () => {
408
+ const searchService = new MockSearchService();
409
+ const collectionNameCache = new MockCollectionNameCache();
410
+
411
+ const el = await fixture<CollectionBrowser>(
412
+ html`<collection-browser
413
+ .searchService=${searchService}
414
+ .collectionNameCache=${collectionNameCache}
415
+ >
416
+ </collection-browser>`
417
+ );
418
+
419
+ el.baseQuery = 'collection:foo';
420
+ await el.updateComplete;
421
+
422
+ expect(collectionNameCache.preloadIdentifiersRequested).to.deep.equal([
423
+ 'foo',
424
+ 'bar',
425
+ 'baz',
426
+ 'boop',
427
+ ]);
428
+ });
429
+
430
+ it('keeps search results from fetch if no change to query or sort param', async () => {
431
+ const resultsSpy = sinon.spy();
432
+ const searchService = new MockSearchService({
433
+ asyncResponse: true,
434
+ resultsSpy,
435
+ });
436
+
437
+ const el = await fixture<CollectionBrowser>(
438
+ html`<collection-browser .searchService=${searchService}>
439
+ </collection-browser>`
440
+ );
441
+
442
+ el.baseQuery = 'with-sort';
443
+ el.sortParam = { field: 'foo', direction: 'asc' };
444
+ await el.updateComplete;
445
+
446
+ await el.fetchPage(2);
447
+
448
+ // If there is no change to the query or sort param during the fetch, the results
449
+ // should be read.
450
+ expect(resultsSpy.callCount).to.be.greaterThanOrEqual(1);
451
+ });
452
+
453
+ it('discards obsolete search results if sort params changed before arrival', async () => {
454
+ const resultsSpy = sinon.spy();
455
+ const searchService = new MockSearchService({
456
+ asyncResponse: true,
457
+ resultsSpy,
458
+ });
459
+
460
+ const el = await fixture<CollectionBrowser>(
461
+ html`<collection-browser .searchService=${searchService}>
462
+ </collection-browser>`
463
+ );
464
+
465
+ el.baseQuery = 'with-sort';
466
+ el.sortParam = { field: 'foo', direction: 'asc' };
467
+ await el.updateComplete;
468
+
469
+ const fetchPromise = el.fetchPage(2);
470
+ el.sortParam = { field: 'foo', direction: 'desc' };
471
+ await fetchPromise;
472
+
473
+ // If the different sort param causes the results to be discarded,
474
+ // the results array should never be read.
475
+ expect(resultsSpy.callCount).to.equal(0);
476
+ });
477
+
478
+ it('discards obsolete search results if sort param added before arrival', async () => {
479
+ const resultsSpy = sinon.spy();
480
+ const searchService = new MockSearchService({
481
+ asyncResponse: true,
482
+ resultsSpy,
483
+ });
484
+
485
+ const el = await fixture<CollectionBrowser>(
486
+ html`<collection-browser .searchService=${searchService}>
487
+ </collection-browser>`
488
+ );
489
+
490
+ el.baseQuery = 'single-result';
491
+ await el.updateComplete;
492
+
493
+ const fetchPromise = el.fetchPage(2);
494
+ el.sortParam = { field: 'foo', direction: 'asc' };
495
+ await fetchPromise;
496
+
497
+ // If the different sort param causes the results to be discarded,
498
+ // the results array should never be read.
499
+ expect(resultsSpy.callCount).to.equal(0);
500
+ });
501
+
502
+ it('discards obsolete search results if sort param cleared before arrival', async () => {
503
+ const resultsSpy = sinon.spy();
504
+ const searchService = new MockSearchService({
505
+ asyncResponse: true,
506
+ resultsSpy,
507
+ });
508
+
509
+ const el = await fixture<CollectionBrowser>(
510
+ html`<collection-browser .searchService=${searchService}>
511
+ </collection-browser>`
512
+ );
513
+
514
+ el.baseQuery = 'with-sort';
515
+ await el.updateComplete;
516
+
517
+ const fetchPromise = el.fetchPage(2);
518
+ el.sortParam = null;
519
+ await fetchPromise;
520
+
521
+ // If the different sort param causes the results to be discarded,
522
+ // the results array should never be read.
523
+ expect(resultsSpy.callCount).to.equal(0);
524
+ });
525
+
526
+ it('sets sort properties when user changes sort', async () => {
527
+ const searchService = new MockSearchService();
528
+ const el = await fixture<CollectionBrowser>(
529
+ html`<collection-browser .searchService=${searchService}>
530
+ </collection-browser>`
531
+ );
532
+
533
+ expect(el.selectedSort).to.equal(SortField.relevance);
534
+
535
+ el.baseQuery = 'foo';
536
+ await el.updateComplete;
537
+
538
+ const sortBar = el.shadowRoot?.querySelector('sort-filter-bar');
539
+ const sortSelector = sortBar?.shadowRoot?.querySelector(
540
+ '#desktop-sort-selector'
541
+ );
542
+ expect(sortSelector).to.exist;
543
+
544
+ // Click the title sorter
545
+ [...(sortSelector?.children as HTMLCollection & Iterable<any>)] // tsc doesn't know children is iterable
546
+ .find(child => child.textContent?.trim() === 'Title')
547
+ ?.querySelector('a[href]')
548
+ ?.click();
549
+
550
+ await el.updateComplete;
551
+
552
+ expect(el.selectedSort).to.equal(SortField.title);
553
+ });
554
+
555
+ it('scrolls to page', async () => {
556
+ const searchService = new MockSearchService();
557
+ const el = await fixture<CollectionBrowser>(
558
+ html`<collection-browser .searchService=${searchService}>
559
+ </collection-browser>`
560
+ );
561
+
562
+ // Infinite scroller won't exist unless there's a base query
563
+ el.baseQuery = 'collection:foo';
564
+ await el.updateComplete;
565
+
566
+ const infiniteScroller = el.shadowRoot?.querySelector(
567
+ 'infinite-scroller'
568
+ ) as InfiniteScroller;
569
+ expect(infiniteScroller).to.exist;
570
+
571
+ const oldScrollToCell = infiniteScroller.scrollToCell;
572
+ const spy = sinon.spy();
573
+ infiniteScroller.scrollToCell = spy;
574
+
575
+ el.goToPage(1);
576
+
577
+ // Give it a second to scroll
578
+ await new Promise(res => {
579
+ setTimeout(res, 1000);
580
+ });
581
+
582
+ expect(spy.callCount).to.equal(1);
583
+
584
+ infiniteScroller.scrollToCell = oldScrollToCell;
585
+ });
586
+
587
+ it('refreshes when certain properties change - with some analytics event sampling', async () => {
588
+ const mockAnalyticsHandler = new MockAnalyticsHandler();
589
+ const searchService = new MockSearchService();
590
+ const collectionNameCache = new MockCollectionNameCache();
591
+ const el = await fixture<CollectionBrowser>(
592
+ html`<collection-browser
593
+ .analyticsHandler=${mockAnalyticsHandler}
594
+ .searchService=${searchService}
595
+ .collectionNameCache=${collectionNameCache}
596
+ ></collection-browser>`
597
+ );
598
+ const infiniteScrollerRefreshSpy = sinon.spy();
599
+
600
+ // Infinite scroller won't exist unless there's a base query
601
+ el.baseQuery = 'collection:foo';
602
+ await el.updateComplete;
603
+
604
+ const infiniteScroller = el.shadowRoot?.querySelector('infinite-scroller');
605
+ (infiniteScroller as InfiniteScroller).reload = infiniteScrollerRefreshSpy;
606
+ expect(infiniteScrollerRefreshSpy.called).to.be.false;
607
+ expect(infiniteScrollerRefreshSpy.callCount).to.equal(0);
608
+
609
+ // testing: `loggedIn`
610
+ el.loggedIn = true;
611
+ await el.updateComplete;
612
+ expect(infiniteScrollerRefreshSpy.called).to.be.true;
613
+ expect(infiniteScrollerRefreshSpy.callCount).to.equal(1);
614
+
615
+ el.loggedIn = false;
616
+ await el.updateComplete;
617
+ expect(infiniteScrollerRefreshSpy.callCount).to.equal(2);
618
+
619
+ // testing: `displayMode`
620
+ el.displayMode = 'list-compact';
621
+ el.searchContext = 'beta-search';
622
+ await el.updateComplete;
623
+ expect(infiniteScrollerRefreshSpy.callCount).to.equal(3);
624
+
625
+ expect(mockAnalyticsHandler.callCategory).to.equal('beta-search');
626
+ expect(mockAnalyticsHandler.callAction).to.equal('displayMode');
627
+ expect(mockAnalyticsHandler.callLabel).to.equal('list-compact');
628
+
629
+ el.displayMode = 'list-detail';
630
+ await el.updateComplete;
631
+ expect(infiniteScrollerRefreshSpy.callCount).to.equal(4);
632
+
633
+ expect(mockAnalyticsHandler.callCategory).to.equal('beta-search');
634
+ expect(mockAnalyticsHandler.callAction).to.equal('displayMode');
635
+ expect(mockAnalyticsHandler.callLabel).to.equal('list-detail');
636
+
637
+ // testing: `baseNavigationUrl`
638
+ el.baseNavigationUrl = 'https://funtestsite.com';
639
+ await el.updateComplete;
640
+ expect(infiniteScrollerRefreshSpy.callCount).to.equal(5);
641
+
642
+ // testing: `baseImageUrl`
643
+ el.baseImageUrl = 'https://funtestsiteforimages.com';
644
+ await el.updateComplete;
645
+ expect(infiniteScrollerRefreshSpy.callCount).to.equal(6);
646
+ });
647
+
648
+ it('query the search service for single result', async () => {
649
+ const searchService = new MockSearchService();
650
+
651
+ const el = await fixture<CollectionBrowser>(
652
+ html`<collection-browser .searchService=${searchService}>
653
+ </collection-browser>`
654
+ );
655
+
656
+ el.baseQuery = 'single-result';
657
+ await el.updateComplete;
658
+
659
+ expect(
660
+ el.shadowRoot?.querySelector('#big-results-label')?.textContent
661
+ ).to.contains('Result');
662
+ });
663
+
664
+ it('`searchContext` prop helps describe where component is being used', async () => {
665
+ const el = await fixture<CollectionBrowser>(
666
+ html`<collection-browser></collection-browser>`
667
+ );
668
+
669
+ expect(el.searchContext).to.equal(analyticsCategories.default);
670
+
671
+ el.searchContext = 'unicorn-search';
672
+ await el.updateComplete;
673
+
674
+ expect(el.searchContext).to.equal('unicorn-search');
675
+
676
+ // property is reflected as attribute
677
+ expect(el.getAttribute('searchcontext')).to.equal('unicorn-search');
678
+ });
679
+ });