@internetarchive/collection-browser 0.4.16-alpha.9 → 0.4.16

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 (63) hide show
  1. package/dist/src/app-root.js +12 -0
  2. package/dist/src/app-root.js.map +1 -1
  3. package/dist/src/collection-browser.d.ts +53 -14
  4. package/dist/src/collection-browser.js +264 -110
  5. package/dist/src/collection-browser.js.map +1 -1
  6. package/dist/src/collection-facets/facets-template.d.ts +3 -0
  7. package/dist/src/collection-facets/facets-template.js +20 -1
  8. package/dist/src/collection-facets/facets-template.js.map +1 -1
  9. package/dist/src/collection-facets/more-facets-content.js +7 -4
  10. package/dist/src/collection-facets/more-facets-content.js.map +1 -1
  11. package/dist/src/collection-facets.d.ts +0 -2
  12. package/dist/src/collection-facets.js +1 -4
  13. package/dist/src/collection-facets.js.map +1 -1
  14. package/dist/src/empty-placeholder.js +1 -0
  15. package/dist/src/empty-placeholder.js.map +1 -1
  16. package/dist/src/models.d.ts +5 -0
  17. package/dist/src/models.js.map +1 -1
  18. package/dist/src/tiles/grid/item-tile.js +10 -3
  19. package/dist/src/tiles/grid/item-tile.js.map +1 -1
  20. package/dist/src/tiles/list/tile-list-compact.d.ts +1 -0
  21. package/dist/src/tiles/list/tile-list-compact.js +13 -1
  22. package/dist/src/tiles/list/tile-list-compact.js.map +1 -1
  23. package/dist/src/tiles/list/tile-list.js +10 -1
  24. package/dist/src/tiles/list/tile-list.js.map +1 -1
  25. package/dist/src/utils/format-date.d.ts +1 -1
  26. package/dist/src/utils/format-date.js +3 -0
  27. package/dist/src/utils/format-date.js.map +1 -1
  28. package/dist/src/utils/local-date-from-utc.d.ts +9 -0
  29. package/dist/src/utils/local-date-from-utc.js +16 -0
  30. package/dist/src/utils/local-date-from-utc.js.map +1 -0
  31. package/dist/test/collection-browser.test.js +41 -10
  32. package/dist/test/collection-browser.test.js.map +1 -1
  33. package/dist/test/collection-facets/facets-template.test.js +80 -0
  34. package/dist/test/collection-facets/facets-template.test.js.map +1 -1
  35. package/dist/test/tiles/grid/item-tile.test.js +124 -3
  36. package/dist/test/tiles/grid/item-tile.test.js.map +1 -1
  37. package/dist/test/tiles/list/tile-list-compact.test.js +65 -20
  38. package/dist/test/tiles/list/tile-list-compact.test.js.map +1 -1
  39. package/dist/test/tiles/list/tile-list.test.js +106 -4
  40. package/dist/test/tiles/list/tile-list.test.js.map +1 -1
  41. package/dist/test/utils/local-date-from-utc.test.d.ts +1 -0
  42. package/dist/test/utils/local-date-from-utc.test.js +27 -0
  43. package/dist/test/utils/local-date-from-utc.test.js.map +1 -0
  44. package/index.html +1 -0
  45. package/package.json +1 -1
  46. package/src/app-root.ts +12 -0
  47. package/src/collection-browser.ts +294 -112
  48. package/src/collection-facets/facets-template.ts +32 -1
  49. package/src/collection-facets/more-facets-content.ts +4 -1
  50. package/src/collection-facets.ts +1 -8
  51. package/src/empty-placeholder.ts +1 -0
  52. package/src/models.ts +6 -0
  53. package/src/tiles/grid/item-tile.ts +11 -4
  54. package/src/tiles/list/tile-list-compact.ts +16 -2
  55. package/src/tiles/list/tile-list.ts +12 -5
  56. package/src/utils/format-date.ts +4 -0
  57. package/src/utils/local-date-from-utc.ts +15 -0
  58. package/test/collection-browser.test.ts +57 -12
  59. package/test/collection-facets/facets-template.test.ts +98 -0
  60. package/test/tiles/grid/item-tile.test.ts +145 -3
  61. package/test/tiles/list/tile-list-compact.test.ts +70 -19
  62. package/test/tiles/list/tile-list.test.ts +118 -4
  63. package/test/utils/local-date-from-utc.test.ts +37 -0
@@ -10,6 +10,8 @@ import {
10
10
  FacetBucket,
11
11
  SelectedFacets,
12
12
  getDefaultSelectedFacets,
13
+ FacetEventDetails,
14
+ FacetState,
13
15
  } from '../models';
14
16
 
15
17
  @customElement('facets-template')
@@ -31,6 +33,12 @@ export class FacetsTemplate extends LitElement {
31
33
  } else {
32
34
  this.facetUnchecked(name as FacetOption, value);
33
35
  }
36
+
37
+ this.dispatchFacetClickEvent(
38
+ name as FacetOption,
39
+ this.getFacetState(checked, negative),
40
+ negative
41
+ );
34
42
  }
35
43
 
36
44
  private facetChecked(
@@ -49,7 +57,7 @@ export class FacetsTemplate extends LitElement {
49
57
  newFacets = getDefaultSelectedFacets();
50
58
  }
51
59
  newFacets[key][value] = {
52
- state: negative ? 'hidden' : 'selected',
60
+ state: this.getFacetState(true, negative),
53
61
  count,
54
62
  } as FacetBucket;
55
63
 
@@ -73,6 +81,29 @@ export class FacetsTemplate extends LitElement {
73
81
  this.dispatchSelectedFacetsChanged();
74
82
  }
75
83
 
84
+ /** Returns the composed facet state corresponding to a positive or negative facet's checked state */
85
+ private getFacetState(checked: boolean, negative: boolean): FacetState {
86
+ let state: FacetState;
87
+ if (checked) {
88
+ state = negative ? 'hidden' : 'selected';
89
+ } else {
90
+ state = 'none';
91
+ }
92
+ return state;
93
+ }
94
+
95
+ private dispatchFacetClickEvent(
96
+ key: FacetOption,
97
+ state: FacetState,
98
+ negative: boolean
99
+ ) {
100
+ const event = new CustomEvent<FacetEventDetails>('facetClick', {
101
+ detail: { key, state, negative },
102
+ composed: true,
103
+ });
104
+ this.dispatchEvent(event);
105
+ }
106
+
76
107
  private dispatchSelectedFacetsChanged() {
77
108
  const event = new CustomEvent<SelectedFacets>('selectedFacetsChanged', {
78
109
  detail: this.selectedFacets,
@@ -130,13 +130,16 @@ export class MoreFacetsContent extends LitElement {
130
130
  * - this.aggregations - hold result of search service and being used for further processing.
131
131
  */
132
132
  async updateSpecificFacets(): Promise<void> {
133
+ const trimmedQuery = this.query?.trim();
134
+ if (!trimmedQuery) return;
135
+
133
136
  const aggregations = {
134
137
  simpleParams: [this.facetAggregationKey as string],
135
138
  };
136
139
  const aggregationsSize = 65535; // todo - do we want to have all the records at once?
137
140
 
138
141
  const params: SearchParams = {
139
- query: this.query as string,
142
+ query: trimmedQuery,
140
143
  filters: this.filterMap,
141
144
  aggregations,
142
145
  aggregationsSize,
@@ -99,13 +99,6 @@ export class CollectionFacets extends LitElement {
99
99
  @property({ type: Object, attribute: false })
100
100
  collectionNameCache?: CollectionNameCacheInterface;
101
101
 
102
- /** Fires when a facet is clicked */
103
- @property({ type: Function }) onFacetClick?: (
104
- name: FacetOption,
105
- facetChecked: boolean,
106
- negative: boolean
107
- ) => void;
108
-
109
102
  @state() openFacets: Record<FacetOption, boolean> = {
110
103
  subject: false,
111
104
  lending: false,
@@ -554,7 +547,7 @@ export class CollectionFacets extends LitElement {
554
547
  transform: rotate(90deg);
555
548
  }
556
549
 
557
- .facet-group {
550
+ .facet-group:not(:last-child) {
558
551
  margin-bottom: 2rem;
559
552
  }
560
553
 
@@ -119,6 +119,7 @@ export class EmptyPlaceholder extends LitElement {
119
119
 
120
120
  .error-details {
121
121
  font-size: 1.2rem;
122
+ word-break: break-word;
122
123
  }
123
124
  `;
124
125
  }
package/src/models.ts CHANGED
@@ -220,6 +220,12 @@ export interface FacetGroup {
220
220
  buckets: FacetBucket[];
221
221
  }
222
222
 
223
+ export type FacetEventDetails = {
224
+ key: FacetOption;
225
+ state: FacetState;
226
+ negative: boolean;
227
+ };
228
+
223
229
  export type FacetValue = string;
224
230
 
225
231
  export type SelectedFacets = Record<
@@ -11,7 +11,8 @@ import { customElement, property } from 'lit/decorators.js';
11
11
  import { ifDefined } from 'lit/directives/if-defined.js';
12
12
  import type { SortParam } from '@internetarchive/search-service';
13
13
 
14
- import { formatDate } from '../../utils/format-date';
14
+ import { DateFormat, formatDate } from '../../utils/format-date';
15
+ import { isFirstMillisecondOfUTCYear } from '../../utils/local-date-from-utc';
15
16
  import type { TileModel } from '../../models';
16
17
 
17
18
  import { baseTileStyles } from './styles/tile-grid-shared-styles';
@@ -104,10 +105,16 @@ export class ItemTile extends LitElement {
104
105
 
105
106
  private get sortedDateInfoTemplate() {
106
107
  let sortedValue;
108
+ let format: DateFormat = 'long';
107
109
  switch (this.sortParam?.field) {
108
- case 'date':
109
- sortedValue = { field: 'published', value: this.model?.datePublished };
110
+ case 'date': {
111
+ const datePublished = this.model?.datePublished;
112
+ sortedValue = { field: 'published', value: datePublished };
113
+ if (isFirstMillisecondOfUTCYear(datePublished)) {
114
+ format = 'year-only';
115
+ }
110
116
  break;
117
+ }
111
118
  case 'reviewdate':
112
119
  sortedValue = { field: 'reviewed', value: this.model?.dateReviewed };
113
120
  break;
@@ -127,7 +134,7 @@ export class ItemTile extends LitElement {
127
134
  return html`
128
135
  <div class="date-sorted-by truncated">
129
136
  <span>
130
- ${sortedValue?.field} ${formatDate(sortedValue?.value, 'long')}
137
+ ${sortedValue?.field} ${formatDate(sortedValue?.value, format)}
131
138
  </span>
132
139
  </div>
133
140
  `;
@@ -6,6 +6,7 @@ import type { TileModel } from '../../models';
6
6
 
7
7
  import { formatCount, NumberFormat } from '../../utils/format-count';
8
8
  import { formatDate, DateFormat } from '../../utils/format-date';
9
+ import { isFirstMillisecondOfUTCYear } from '../../utils/local-date-from-utc';
9
10
  import { accountLabel } from './account-label';
10
11
 
11
12
  import '../image-block';
@@ -49,7 +50,7 @@ export class TileListCompact extends LitElement {
49
50
  ? accountLabel(this.model?.dateAdded)
50
51
  : DOMPurify.sanitize(this.model?.creator ?? '')}
51
52
  </div>
52
- <div id="date">${formatDate(this.date, this.formatSize)}</div>
53
+ <div id="date">${formatDate(this.date, this.dateFormatSize)}</div>
53
54
  <div id="icon">
54
55
  <mediatype-icon
55
56
  .mediatype=${this.model?.mediatype}
@@ -111,7 +112,20 @@ export class TileListCompact extends LitElement {
111
112
  return 'desktop';
112
113
  }
113
114
 
114
- private get formatSize(): DateFormat | NumberFormat {
115
+ private get dateFormatSize(): DateFormat {
116
+ // If we're showing a date published of Jan 1 at midnight, only show the year.
117
+ // This is because items with only a year for their publication date are normalized to
118
+ // Jan 1 at midnight timestamps in the search engine documents.
119
+ if (
120
+ (!this.sortParam?.field || this.sortParam.field === 'date') && // No sort or date published
121
+ isFirstMillisecondOfUTCYear(this.model?.datePublished)
122
+ ) {
123
+ return 'year-only';
124
+ }
125
+ return this.formatSize;
126
+ }
127
+
128
+ private get formatSize(): NumberFormat {
115
129
  if (
116
130
  this.mobileBreakpoint &&
117
131
  this.currentWidth &&
@@ -21,6 +21,7 @@ import { dateLabel } from './date-label';
21
21
  import { accountLabel } from './account-label';
22
22
  import { formatCount, NumberFormat } from '../../utils/format-count';
23
23
  import { formatDate, DateFormat } from '../../utils/format-date';
24
+ import { isFirstMillisecondOfUTCYear } from '../../utils/local-date-from-utc';
24
25
 
25
26
  import '../image-block';
26
27
  import '../mediatype-icon';
@@ -205,10 +206,16 @@ export class TileList extends LitElement {
205
206
  }
206
207
 
207
208
  private get datePublishedTemplate() {
208
- return this.metadataTemplate(
209
- formatDate(this.model?.datePublished, 'long'),
210
- 'Published'
211
- );
209
+ // If we're showing a date published of Jan 1 at midnight, only show the year.
210
+ // This is because items with only a year for their publication date are normalized to
211
+ // Jan 1 at midnight timestamps in the search engine documents.
212
+ const date: Date | undefined = this.model?.datePublished;
213
+ let format: DateFormat = 'long';
214
+ if (isFirstMillisecondOfUTCYear(date)) {
215
+ format = 'year-only';
216
+ }
217
+
218
+ return this.metadataTemplate(formatDate(date, format), 'Published');
212
219
  }
213
220
 
214
221
  // Show date label/value when sorted by date type
@@ -430,7 +437,7 @@ export class TileList extends LitElement {
430
437
  return 'desktop';
431
438
  }
432
439
 
433
- private get formatSize(): DateFormat | NumberFormat {
440
+ private get formatSize(): NumberFormat {
434
441
  if (
435
442
  this.mobileBreakpoint &&
436
443
  this.currentWidth &&
@@ -3,6 +3,7 @@
3
3
  * Override browser timezone to always display same date as in data
4
4
  */
5
5
  export type DateFormat =
6
+ | 'year-only' // 2020
6
7
  | 'short' // Dec 2020
7
8
  | 'long'; // Dec 20, 2020
8
9
 
@@ -18,6 +19,9 @@ export function formatDate(
18
19
  timeZone: 'UTC', // Override browser timezone
19
20
  };
20
21
  switch (format) {
22
+ case 'year-only':
23
+ options.year = 'numeric';
24
+ break;
21
25
  case 'short':
22
26
  options.month = 'short';
23
27
  options.year = 'numeric';
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Converts a given UTC date into the equivalent local-timestamp one.
3
+ */
4
+ export function localDateFromUTC(date: Date): Date {
5
+ return new Date(date.getTime() - date.getTimezoneOffset() * 1000 * 60);
6
+ }
7
+
8
+ /**
9
+ * Returns whether a given UTC date corresponds to the first
10
+ * millisecond of the year (e.g., Jan 1 at exactly midnight).
11
+ */
12
+ export function isFirstMillisecondOfUTCYear(date?: Date): boolean {
13
+ if (!date) return false;
14
+ return localDateFromUTC(date).toISOString().endsWith('-01-01T00:00:00.000Z');
15
+ }
@@ -3,7 +3,7 @@ import { aTimeout, expect, fixture } from '@open-wc/testing';
3
3
  import { html } from 'lit';
4
4
  import sinon from 'sinon';
5
5
  import type { InfiniteScroller } from '@internetarchive/infinite-scroller';
6
- import { SearchType } from '@internetarchive/search-service';
6
+ import { FilterConstraint, SearchType } from '@internetarchive/search-service';
7
7
  import type { HistogramDateRange } from '@internetarchive/histogram-date-range';
8
8
  import type { CollectionBrowser } from '../src/collection-browser';
9
9
  import '../src/collection-browser';
@@ -174,12 +174,20 @@ describe('Collection Browser', () => {
174
174
  el.selectedFacets = mockedSelectedFacets;
175
175
  await el.updateComplete;
176
176
 
177
- el.facetClickHandler('mediatype', true, false);
177
+ el.facetClickHandler(
178
+ new CustomEvent('facetClick', {
179
+ detail: { key: 'mediatype', state: 'selected', negative: false },
180
+ })
181
+ );
178
182
  expect(mockAnalyticsHandler.callCategory).to.equal('search-service');
179
183
  expect(mockAnalyticsHandler.callAction).to.equal('facetSelected');
180
184
  expect(mockAnalyticsHandler.callLabel).to.equal('mediatype');
181
185
 
182
- el.facetClickHandler('mediatype', false, false);
186
+ el.facetClickHandler(
187
+ new CustomEvent('facetClick', {
188
+ detail: { key: 'mediatype', state: 'none', negative: false },
189
+ })
190
+ );
183
191
  expect(el.selectedFacets).to.equal(mockedSelectedFacets);
184
192
  expect(mockAnalyticsHandler.callCategory).to.equal('search-service');
185
193
  expect(mockAnalyticsHandler.callAction).to.equal('facetDeselected');
@@ -208,12 +216,20 @@ describe('Collection Browser', () => {
208
216
  el.selectedFacets = mockedSelectedFacets;
209
217
  await el.updateComplete;
210
218
 
211
- el.facetClickHandler('mediatype', true, true);
219
+ el.facetClickHandler(
220
+ new CustomEvent('facetClick', {
221
+ detail: { key: 'mediatype', state: 'hidden', negative: true },
222
+ })
223
+ );
212
224
  expect(mockAnalyticsHandler.callCategory).to.equal('beta-search-service');
213
225
  expect(mockAnalyticsHandler.callAction).to.equal('facetNegativeSelected');
214
226
  expect(mockAnalyticsHandler.callLabel).to.equal('mediatype');
215
227
 
216
- el.facetClickHandler('mediatype', false, true);
228
+ el.facetClickHandler(
229
+ new CustomEvent('facetClick', {
230
+ detail: { key: 'mediatype', state: 'none', negative: true },
231
+ })
232
+ );
217
233
  expect(el.selectedFacets).to.equal(mockedSelectedFacets);
218
234
  expect(mockAnalyticsHandler.callCategory).to.equal('beta-search-service');
219
235
  expect(mockAnalyticsHandler.callAction).to.equal('facetNegativeDeselected');
@@ -727,8 +743,9 @@ describe('Collection Browser', () => {
727
743
  el.selectedTitleFilter = 'X';
728
744
  await el.updateComplete;
729
745
 
730
- expect(searchService.searchParams?.query).to.equal(
731
- 'first-title AND firstTitle:X'
746
+ expect(searchService.searchParams?.query).to.equal('first-title');
747
+ expect(searchService.searchParams?.filters?.firstTitle?.X).to.equal(
748
+ FilterConstraint.INCLUDE
732
749
  );
733
750
  });
734
751
 
@@ -745,8 +762,9 @@ describe('Collection Browser', () => {
745
762
  el.selectedCreatorFilter = 'X';
746
763
  await el.updateComplete;
747
764
 
748
- expect(searchService.searchParams?.query).to.equal(
749
- 'first-creator AND firstCreator:X'
765
+ expect(searchService.searchParams?.query).to.equal('first-creator');
766
+ expect(searchService.searchParams?.filters?.firstCreator?.X).to.equal(
767
+ FilterConstraint.INCLUDE
750
768
  );
751
769
  });
752
770
 
@@ -776,9 +794,7 @@ describe('Collection Browser', () => {
776
794
  el.selectedCreatorFilter = 'X';
777
795
  await el.updateComplete;
778
796
 
779
- expect(searchService.searchParams?.query).to.equal(
780
- 'first-creator AND firstCreator:X'
781
- );
797
+ expect(searchService.searchParams?.query).to.equal('first-creator');
782
798
  expect(searchService.searchParams?.filters).to.deep.equal({
783
799
  collection: {
784
800
  foo: 'inc',
@@ -787,9 +803,38 @@ describe('Collection Browser', () => {
787
803
  '1950': 'gte',
788
804
  '1970': 'lte',
789
805
  },
806
+ firstCreator: {
807
+ X: 'inc',
808
+ },
790
809
  });
791
810
  });
792
811
 
812
+ it('resets letter filters when query changes', async () => {
813
+ const searchService = new MockSearchService();
814
+ const el = await fixture<CollectionBrowser>(
815
+ html`<collection-browser .searchService=${searchService}>
816
+ </collection-browser>`
817
+ );
818
+
819
+ el.baseQuery = 'first-creator';
820
+ el.selectedSort = 'creator' as SortField;
821
+ el.sortDirection = 'asc';
822
+ el.selectedCreatorFilter = 'X';
823
+ await el.updateComplete;
824
+ await nextTick();
825
+
826
+ expect(searchService.searchParams?.query).to.equal('first-creator');
827
+ expect(searchService.searchParams?.filters?.firstCreator?.X).to.equal(
828
+ FilterConstraint.INCLUDE
829
+ );
830
+
831
+ el.baseQuery = 'collection:foo';
832
+ await el.updateComplete;
833
+
834
+ expect(searchService.searchParams?.query).to.equal('collection:foo');
835
+ expect(searchService.searchParams?.filters?.firstCreator).not.to.exist;
836
+ });
837
+
793
838
  it('sets date range query when date picker selection changed', async () => {
794
839
  const searchService = new MockSearchService();
795
840
  const el = await fixture<CollectionBrowser>(
@@ -1,7 +1,9 @@
1
1
  import { expect, fixture } from '@open-wc/testing';
2
+ import sinon from 'sinon';
2
3
  import { html } from 'lit';
3
4
  import type { FacetsTemplate } from '../../src/collection-facets/facets-template';
4
5
  import '../../src/collection-facets/facets-template';
6
+ import { getDefaultSelectedFacets, FacetEventDetails } from '../../src/models';
5
7
 
6
8
  const facetGroup = {
7
9
  title: 'Media Type',
@@ -102,4 +104,100 @@ describe('Render facets', () => {
102
104
  'Hide mediatype: movies'
103
105
  );
104
106
  });
107
+
108
+ it('emits facetClick events for normal facets', async () => {
109
+ const facetClickSpy = sinon.spy();
110
+ const mediatypeGroup = {
111
+ title: 'Media Type',
112
+ key: 'mediatype',
113
+ buckets: [
114
+ { displayText: 'audio', key: 'audio', count: 42, state: 'none' },
115
+ ],
116
+ };
117
+ const selectedFacets = getDefaultSelectedFacets();
118
+ const el = await fixture<FacetsTemplate>(
119
+ html`<facets-template
120
+ .facetGroup=${mediatypeGroup}
121
+ .selectedFacets=${selectedFacets}
122
+ @facetClick=${facetClickSpy}
123
+ ></facets-template>`
124
+ );
125
+
126
+ const checkbox = el.shadowRoot?.querySelector(
127
+ '.select-facet-checkbox'
128
+ ) as HTMLInputElement;
129
+ expect(checkbox).to.exist;
130
+
131
+ // Select it
132
+ checkbox.click();
133
+ await el.updateComplete;
134
+ expect(facetClickSpy.callCount).to.equal(1);
135
+
136
+ const selectEvent = facetClickSpy
137
+ .args[0][0] as CustomEvent<FacetEventDetails>;
138
+ expect(selectEvent).to.exist;
139
+ expect(selectEvent?.detail?.key).to.equal('mediatype');
140
+ expect(selectEvent?.detail?.state).to.equal('selected');
141
+ expect(selectEvent?.detail?.negative).to.be.false;
142
+
143
+ // Unselect it
144
+ checkbox.click();
145
+ await el.updateComplete;
146
+ expect(facetClickSpy.callCount).to.equal(2);
147
+
148
+ const unselectEvent = facetClickSpy
149
+ .args[1][0] as CustomEvent<FacetEventDetails>;
150
+ expect(unselectEvent).to.exist;
151
+ expect(unselectEvent?.detail?.key).to.equal('mediatype');
152
+ expect(unselectEvent?.detail?.state).to.equal('none');
153
+ expect(unselectEvent?.detail?.negative).to.be.false;
154
+ });
155
+
156
+ it('emits facetClick events for negative facets', async () => {
157
+ const facetClickSpy = sinon.spy();
158
+ const mediatypeGroup = {
159
+ title: 'Media Type',
160
+ key: 'mediatype',
161
+ buckets: [
162
+ { displayText: 'audio', key: 'audio', count: 42, state: 'none' },
163
+ ],
164
+ };
165
+ const selectedFacets = getDefaultSelectedFacets();
166
+ const el = await fixture<FacetsTemplate>(
167
+ html`<facets-template
168
+ .facetGroup=${mediatypeGroup}
169
+ .selectedFacets=${selectedFacets}
170
+ @facetClick=${facetClickSpy}
171
+ ></facets-template>`
172
+ );
173
+
174
+ const checkbox = el.shadowRoot?.querySelector(
175
+ '.hide-facet-checkbox'
176
+ ) as HTMLInputElement;
177
+ expect(checkbox).to.exist;
178
+
179
+ // Select it
180
+ checkbox.click();
181
+ await el.updateComplete;
182
+ expect(facetClickSpy.callCount).to.equal(1);
183
+
184
+ const selectEvent = facetClickSpy
185
+ .args[0][0] as CustomEvent<FacetEventDetails>;
186
+ expect(selectEvent).to.exist;
187
+ expect(selectEvent?.detail?.key).to.equal('mediatype');
188
+ expect(selectEvent?.detail?.state).to.equal('hidden');
189
+ expect(selectEvent?.detail?.negative).to.be.true;
190
+
191
+ // Unselect it
192
+ checkbox.click();
193
+ await el.updateComplete;
194
+ expect(facetClickSpy.callCount).to.equal(2);
195
+
196
+ const unselectEvent = facetClickSpy
197
+ .args[1][0] as CustomEvent<FacetEventDetails>;
198
+ expect(unselectEvent).to.exist;
199
+ expect(unselectEvent?.detail?.key).to.equal('mediatype');
200
+ expect(unselectEvent?.detail?.state).to.equal('none');
201
+ expect(unselectEvent?.detail?.negative).to.be.true;
202
+ });
105
203
  });