@internetarchive/collection-browser 3.3.1 → 3.3.3

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 (84) hide show
  1. package/.editorconfig +29 -29
  2. package/.github/workflows/ci.yml +27 -27
  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/.prettierignore +1 -1
  8. package/LICENSE +661 -661
  9. package/README.md +83 -83
  10. package/dist/src/collection-browser.js +683 -683
  11. package/dist/src/collection-browser.js.map +1 -1
  12. package/dist/src/collection-facets/more-facets-content.js +118 -118
  13. package/dist/src/collection-facets/more-facets-content.js.map +1 -1
  14. package/dist/src/collection-facets.js +265 -266
  15. package/dist/src/collection-facets.js.map +1 -1
  16. package/dist/src/data-source/collection-browser-data-source.js.map +1 -1
  17. package/dist/src/data-source/collection-browser-query-state.js.map +1 -1
  18. package/dist/src/data-source/models.js.map +1 -1
  19. package/dist/src/tiles/base-tile-component.js.map +1 -1
  20. package/dist/src/tiles/grid/account-tile.js +36 -36
  21. package/dist/src/tiles/grid/account-tile.js.map +1 -1
  22. package/dist/src/tiles/grid/collection-tile.js +77 -77
  23. package/dist/src/tiles/grid/collection-tile.js.map +1 -1
  24. package/dist/src/tiles/grid/item-tile.js +137 -137
  25. package/dist/src/tiles/grid/item-tile.js.map +1 -1
  26. package/dist/src/tiles/hover/hover-pane-controller.d.ts +9 -1
  27. package/dist/src/tiles/hover/hover-pane-controller.js +105 -37
  28. package/dist/src/tiles/hover/hover-pane-controller.js.map +1 -1
  29. package/dist/src/tiles/hover/tile-hover-pane.d.ts +1 -0
  30. package/dist/src/tiles/hover/tile-hover-pane.js +115 -112
  31. package/dist/src/tiles/hover/tile-hover-pane.js.map +1 -1
  32. package/dist/src/tiles/list/tile-list-compact.js +99 -99
  33. package/dist/src/tiles/list/tile-list-compact.js.map +1 -1
  34. package/dist/src/tiles/list/tile-list.js +297 -297
  35. package/dist/src/tiles/list/tile-list.js.map +1 -1
  36. package/dist/src/tiles/tile-dispatcher.d.ts +4 -1
  37. package/dist/src/tiles/tile-dispatcher.js +231 -204
  38. package/dist/src/tiles/tile-dispatcher.js.map +1 -1
  39. package/dist/src/utils/format-date.js.map +1 -1
  40. package/dist/test/collection-browser.test.js +189 -189
  41. package/dist/test/collection-browser.test.js.map +1 -1
  42. package/dist/test/tiles/grid/item-tile.test.js +77 -77
  43. package/dist/test/tiles/grid/item-tile.test.js.map +1 -1
  44. package/dist/test/tiles/hover/hover-pane-controller.test.js +68 -21
  45. package/dist/test/tiles/hover/hover-pane-controller.test.js.map +1 -1
  46. package/dist/test/tiles/list/tile-list-compact.test.js +70 -70
  47. package/dist/test/tiles/list/tile-list-compact.test.js.map +1 -1
  48. package/dist/test/tiles/list/tile-list.test.js +126 -126
  49. package/dist/test/tiles/list/tile-list.test.js.map +1 -1
  50. package/dist/test/tiles/tile-dispatcher.test.js +130 -52
  51. package/dist/test/tiles/tile-dispatcher.test.js.map +1 -1
  52. package/dist/test/utils/format-date.test.js.map +1 -1
  53. package/eslint.config.mjs +53 -53
  54. package/index.html +24 -24
  55. package/local.archive.org.cert +86 -86
  56. package/local.archive.org.key +27 -27
  57. package/package.json +118 -117
  58. package/renovate.json +6 -6
  59. package/src/collection-browser.ts +2829 -2829
  60. package/src/collection-facets/more-facets-content.ts +639 -639
  61. package/src/collection-facets.ts +994 -995
  62. package/src/data-source/collection-browser-data-source.ts +1401 -1401
  63. package/src/data-source/collection-browser-query-state.ts +65 -65
  64. package/src/data-source/models.ts +43 -43
  65. package/src/tiles/base-tile-component.ts +65 -65
  66. package/src/tiles/grid/account-tile.ts +113 -113
  67. package/src/tiles/grid/collection-tile.ts +163 -163
  68. package/src/tiles/grid/item-tile.ts +340 -340
  69. package/src/tiles/hover/hover-pane-controller.ts +613 -517
  70. package/src/tiles/hover/tile-hover-pane.ts +184 -180
  71. package/src/tiles/list/tile-list-compact.ts +239 -239
  72. package/src/tiles/list/tile-list.ts +700 -700
  73. package/src/tiles/tile-dispatcher.ts +517 -490
  74. package/src/utils/format-date.ts +62 -62
  75. package/test/collection-browser.test.ts +2403 -2403
  76. package/test/tiles/grid/item-tile.test.ts +520 -520
  77. package/test/tiles/hover/hover-pane-controller.test.ts +418 -353
  78. package/test/tiles/list/tile-list-compact.test.ts +282 -282
  79. package/test/tiles/list/tile-list.test.ts +552 -552
  80. package/test/tiles/tile-dispatcher.test.ts +283 -187
  81. package/test/utils/format-date.test.ts +89 -89
  82. package/tsconfig.json +20 -20
  83. package/web-dev-server.config.mjs +30 -30
  84. package/web-test-runner.config.mjs +41 -41
@@ -1,353 +1,418 @@
1
- import { expect, fixture } from '@open-wc/testing';
2
- import { html, LitElement, nothing, TemplateResult } from 'lit';
3
- import { customElement, property, query } from 'lit/decorators.js';
4
- import {
5
- HoverPaneController,
6
- HoverPaneControllerInterface,
7
- HoverPaneControllerOptions,
8
- HoverPaneProperties,
9
- HoverPaneProviderInterface,
10
- } from '../../../src/tiles/hover/hover-pane-controller';
11
- import type { TileHoverPane } from '../../../src/tiles/hover/tile-hover-pane';
12
- import { TileModel } from '../../../src/models';
13
-
14
- @customElement('host-element')
15
- class HostElement extends LitElement implements HoverPaneProviderInterface {
16
- @property({ type: Object }) controllerOptions?: HoverPaneControllerOptions;
17
-
18
- @property({ type: Boolean }) suppressHoverPane: boolean = false;
19
-
20
- @query('tile-hover-pane') hoverPane?: TileHoverPane;
21
-
22
- controller?: HoverPaneControllerInterface;
23
-
24
- render(): TemplateResult {
25
- return html` ${this.controller?.getTemplate()} `;
26
- }
27
-
28
- protected firstUpdated(): void {
29
- this.controller = new HoverPaneController(this, this.controllerOptions);
30
- }
31
-
32
- getHoverPane(): HTMLElement | undefined {
33
- return this.suppressHoverPane ? undefined : this.hoverPane;
34
- }
35
-
36
- getHoverPaneProps(): HoverPaneProperties {
37
- const tileModel = new TileModel({});
38
- tileModel.checked = false;
39
- tileModel.collectionFilesCount = 1;
40
- tileModel.collections = ['foo', 'bar'];
41
- tileModel.collectionSize = 1;
42
- tileModel.commentCount = 1;
43
- tileModel.contentWarning = false;
44
- tileModel.creators = ['foo', 'bar'];
45
- tileModel.favCount = 1;
46
- tileModel.identifier = 'foo';
47
- tileModel.itemCount = 1;
48
- tileModel.loginRequired = false;
49
- tileModel.mediatype = 'data';
50
- tileModel.subjects = ['foo', 'bar'];
51
- tileModel.title = 'foo';
52
- tileModel.viewCount = 1;
53
-
54
- return {
55
- model: tileModel,
56
- loggedIn: false,
57
- suppressBlurring: false,
58
- sortParam: null,
59
- };
60
- }
61
- }
62
-
63
- describe('Hover Pane Controller', () => {
64
- let oldMatchMedia: typeof window.matchMedia;
65
- let oldOnTouchStart: typeof window.ontouchstart;
66
-
67
- before(() => {
68
- oldMatchMedia = window.matchMedia;
69
- oldOnTouchStart = window.ontouchstart;
70
- window.matchMedia = () => ({ matches: true }) as MediaQueryList;
71
- window.ontouchstart = () => {};
72
- });
73
-
74
- after(() => {
75
- window.matchMedia = oldMatchMedia;
76
- window.ontouchstart = oldOnTouchStart;
77
- });
78
-
79
- it('should initially provide empty template', async () => {
80
- const host = await fixture<HostElement>(
81
- html`<host-element></host-element>`,
82
- );
83
- expect(host.controller?.getTemplate()).to.equal(nothing);
84
- });
85
-
86
- it('should produce a hover pane template after mousemove, and hide it after mouseleave', async () => {
87
- const host = await fixture<HostElement>(
88
- html`<host-element
89
- .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
90
- ></host-element>`,
91
- );
92
-
93
- host.dispatchEvent(new MouseEvent('mousemove'));
94
- // Need to wait a tick for the event handlers to run
95
- await new Promise(resolve => {
96
- setTimeout(resolve, 0);
97
- });
98
-
99
- expect(host.controller?.getTemplate()).not.to.equal(nothing); // Is a TemplateResult
100
-
101
- host.dispatchEvent(new MouseEvent('mouseleave'));
102
- // Need to wait for the fade out transition
103
- await new Promise(resolve => {
104
- setTimeout(resolve, 150);
105
- });
106
-
107
- expect(host.controller?.getTemplate()).to.equal(nothing);
108
- });
109
-
110
- it('should produce a hover pane template after mouseenter, even without mousemove', async () => {
111
- const host = await fixture<HostElement>(
112
- html`<host-element
113
- .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
114
- ></host-element>`,
115
- );
116
-
117
- host.dispatchEvent(new MouseEvent('mouseenter'));
118
- // Need to wait a tick for the event handlers to run
119
- await new Promise(resolve => {
120
- setTimeout(resolve, 0);
121
- });
122
-
123
- expect(host.controller?.getTemplate()).not.to.equal(nothing); // Is a TemplateResult
124
- });
125
-
126
- it('should immediately fade back in if mouse enters while fading out', async () => {
127
- const host = await fixture<HostElement>(
128
- html`<host-element
129
- .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
130
- ></host-element>`,
131
- );
132
-
133
- // Enter the host element and wait for the show handlers to run
134
- host.dispatchEvent(new MouseEvent('mousemove'));
135
- await new Promise(resolve => {
136
- setTimeout(resolve, 0);
137
- });
138
-
139
- // Leave the host element so it begins fading out, but not all the way
140
- host.dispatchEvent(new MouseEvent('mouseleave'));
141
- await new Promise(resolve => {
142
- setTimeout(resolve, 20);
143
- });
144
-
145
- // Re-enter the host element and wait long enough that it would disappear
146
- // if the hide were not cancelled
147
- host.dispatchEvent(new MouseEvent('mousemove'));
148
- await new Promise(resolve => {
149
- setTimeout(resolve, 150);
150
- });
151
-
152
- expect(host.controller?.getTemplate()).not.to.equal(nothing);
153
- });
154
-
155
- it('should flip hover pane if it would overflow the viewport', async () => {
156
- const host = await fixture<HostElement>(
157
- html`<host-element
158
- .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
159
- ></host-element>`,
160
- );
161
-
162
- host.dispatchEvent(
163
- new MouseEvent('mousemove', { clientX: 800, clientY: 600 }),
164
- );
165
- // Need to wait a tick for the event handlers to run
166
- await new Promise(resolve => {
167
- setTimeout(resolve, 0);
168
- });
169
- await host.updateComplete;
170
-
171
- expect(host.controller?.getTemplate()).not.to.equal(nothing);
172
- expect(host.getHoverPane()?.getBoundingClientRect()?.right).to.be.lessThan(
173
- window.innerWidth,
174
- );
175
- expect(host.getHoverPane()?.getBoundingClientRect()?.bottom).to.be.lessThan(
176
- window.innerHeight,
177
- );
178
- });
179
-
180
- it('should gracefully handle undefined hover pane from host element', async () => {
181
- const host = await fixture<HostElement>(
182
- html`<host-element
183
- .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
184
- ?suppressHoverPane=${true}
185
- ></host-element>`,
186
- );
187
-
188
- host.dispatchEvent(new MouseEvent('mousemove'));
189
- // Need to wait a tick for the event handlers to run
190
- await new Promise(resolve => {
191
- setTimeout(resolve, 0);
192
- });
193
-
194
- expect(host.controller?.getTemplate()).not.to.equal(nothing);
195
-
196
- host.dispatchEvent(new MouseEvent('mouseleave'));
197
- await new Promise(resolve => {
198
- setTimeout(resolve, 20);
199
- });
200
-
201
- host.dispatchEvent(new MouseEvent('mousemove'));
202
- await new Promise(resolve => {
203
- setTimeout(resolve, 0);
204
- });
205
-
206
- host.dispatchEvent(new MouseEvent('mouseleave'));
207
- // Need to wait for the fade out transition
208
- await new Promise(resolve => {
209
- setTimeout(resolve, 150);
210
- });
211
-
212
- expect(host.controller?.getTemplate()).to.equal(nothing);
213
- });
214
-
215
- describe('Touch & long-press', () => {
216
- const getTouchStartEvent = (host: EventTarget) =>
217
- new TouchEvent('touchstart', {
218
- touches: [new Touch({ identifier: 0, target: host })],
219
- });
220
-
221
- it('should produce a hover pane after long press', async () => {
222
- const host = await fixture<HostElement>(
223
- html`<host-element
224
- .controllerOptions=${{
225
- showDelay: 0,
226
- longPressDelay: 0,
227
- enableLongPress: true,
228
- }}
229
- ></host-element>`,
230
- );
231
-
232
- // Touch the host element and wait for the long press handlers to run
233
- host.dispatchEvent(getTouchStartEvent(host));
234
- await new Promise(resolve => {
235
- setTimeout(resolve, 0);
236
- });
237
-
238
- expect(host.controller?.getTemplate()).not.to.equal(nothing); // Is a TemplateResult
239
- });
240
-
241
- it('should cancel a long press by moving', async () => {
242
- const host = await fixture<HostElement>(
243
- html`<host-element
244
- .controllerOptions=${{
245
- showDelay: 0,
246
- longPressDelay: 100,
247
- enableLongPress: true,
248
- }}
249
- ></host-element>`,
250
- );
251
-
252
- // Touch the host element
253
- host.dispatchEvent(getTouchStartEvent(host));
254
- await new Promise(resolve => {
255
- setTimeout(resolve, 0);
256
- });
257
-
258
- // Move the touch point, cancelling the long press
259
- host.dispatchEvent(new TouchEvent('touchmove'));
260
- await new Promise(resolve => {
261
- setTimeout(resolve, 150);
262
- });
263
-
264
- expect(host.controller?.getTemplate()).to.equal(nothing);
265
- });
266
-
267
- it('should cancel a long press by ending touch', async () => {
268
- const host = await fixture<HostElement>(
269
- html`<host-element
270
- .controllerOptions=${{
271
- showDelay: 0,
272
- longPressDelay: 100,
273
- enableLongPress: true,
274
- }}
275
- ></host-element>`,
276
- );
277
-
278
- // Touch the host element
279
- host.dispatchEvent(getTouchStartEvent(host));
280
- await new Promise(resolve => {
281
- setTimeout(resolve, 0);
282
- });
283
-
284
- // Lift the touch point, cancelling the long press
285
- host.dispatchEvent(new TouchEvent('touchend'));
286
- await new Promise(resolve => {
287
- setTimeout(resolve, 150);
288
- });
289
-
290
- expect(host.controller?.getTemplate()).to.equal(nothing);
291
- });
292
-
293
- it('should cancel a long press by cancelling touch (e.g., too many touch points)', async () => {
294
- const host = await fixture<HostElement>(
295
- html`<host-element
296
- .controllerOptions=${{
297
- showDelay: 0,
298
- longPressDelay: 100,
299
- enableLongPress: true,
300
- }}
301
- ></host-element>`,
302
- );
303
-
304
- // Touch the host element
305
- host.dispatchEvent(getTouchStartEvent(host));
306
- await new Promise(resolve => {
307
- setTimeout(resolve, 0);
308
- });
309
-
310
- // Cancel the touch point, also cancelling the long press
311
- host.dispatchEvent(new TouchEvent('touchcancel'));
312
- await new Promise(resolve => {
313
- setTimeout(resolve, 150);
314
- });
315
-
316
- expect(host.controller?.getTemplate()).to.equal(nothing);
317
- });
318
-
319
- it('should close the hover pane on mobile when touching the backdrop', async () => {
320
- const host = await fixture<HostElement>(
321
- html`<host-element
322
- .controllerOptions=${{
323
- showDelay: 0,
324
- hideDelay: 0,
325
- longPressDelay: 0,
326
- enableLongPress: true,
327
- mobileBreakpoint: 9999, // Ensure we get the mobile view
328
- }}
329
- ></host-element>`,
330
- );
331
-
332
- // Touch the host element
333
- host.dispatchEvent(getTouchStartEvent(host));
334
- await new Promise(resolve => {
335
- setTimeout(resolve, 0);
336
- });
337
-
338
- expect(host.controller?.getTemplate()).not.to.equal(nothing);
339
-
340
- await host.updateComplete;
341
-
342
- // Touch the backdrop
343
- host.shadowRoot
344
- ?.querySelector('#touch-backdrop')
345
- ?.dispatchEvent(new TouchEvent('touchstart'));
346
- await new Promise(resolve => {
347
- setTimeout(resolve, 150);
348
- });
349
-
350
- expect(host.controller?.getTemplate()).to.equal(nothing);
351
- });
352
- });
353
- });
1
+ import { expect, fixture } from '@open-wc/testing';
2
+ import { html, LitElement, nothing, TemplateResult } from 'lit';
3
+ import { customElement, property, query } from 'lit/decorators.js';
4
+ import {
5
+ HoverPaneController,
6
+ HoverPaneControllerInterface,
7
+ HoverPaneControllerOptions,
8
+ HoverPaneProperties,
9
+ HoverPaneProviderInterface,
10
+ } from '../../../src/tiles/hover/hover-pane-controller';
11
+ import type { TileHoverPane } from '../../../src/tiles/hover/tile-hover-pane';
12
+ import { TileModel } from '../../../src/models';
13
+ import sinon from 'sinon';
14
+
15
+ @customElement('host-element')
16
+ class HostElement extends LitElement implements HoverPaneProviderInterface {
17
+ @property({ type: Object }) controllerOptions?: HoverPaneControllerOptions;
18
+
19
+ @property({ type: Boolean }) suppressHoverPane: boolean = false;
20
+
21
+ @query('tile-hover-pane') hoverPane?: TileHoverPane;
22
+
23
+ controller?: HoverPaneControllerInterface;
24
+
25
+ render(): TemplateResult {
26
+ return html` ${this.controller?.getTemplate()} `;
27
+ }
28
+
29
+ protected firstUpdated(): void {
30
+ this.controller = new HoverPaneController(this, this.controllerOptions);
31
+ }
32
+
33
+ acquireFocus(): void {}
34
+
35
+ releaseFocus(): void {}
36
+
37
+ getHoverPane(): HTMLElement | undefined {
38
+ return this.suppressHoverPane ? undefined : this.hoverPane;
39
+ }
40
+
41
+ getHoverPaneProps(): HoverPaneProperties {
42
+ const tileModel = new TileModel({});
43
+ tileModel.checked = false;
44
+ tileModel.collectionFilesCount = 1;
45
+ tileModel.collections = ['foo', 'bar'];
46
+ tileModel.collectionSize = 1;
47
+ tileModel.commentCount = 1;
48
+ tileModel.contentWarning = false;
49
+ tileModel.creators = ['foo', 'bar'];
50
+ tileModel.favCount = 1;
51
+ tileModel.identifier = 'foo';
52
+ tileModel.itemCount = 1;
53
+ tileModel.loginRequired = false;
54
+ tileModel.mediatype = 'data';
55
+ tileModel.subjects = ['foo', 'bar'];
56
+ tileModel.title = 'foo';
57
+ tileModel.viewCount = 1;
58
+
59
+ return {
60
+ model: tileModel,
61
+ loggedIn: false,
62
+ suppressBlurring: false,
63
+ sortParam: null,
64
+ };
65
+ }
66
+ }
67
+
68
+ describe('Hover Pane Controller', () => {
69
+ let oldMatchMedia: typeof window.matchMedia;
70
+ let oldOnTouchStart: typeof window.ontouchstart;
71
+
72
+ before(() => {
73
+ oldMatchMedia = window.matchMedia;
74
+ oldOnTouchStart = window.ontouchstart;
75
+ window.matchMedia = () => ({ matches: true }) as MediaQueryList;
76
+ window.ontouchstart = () => {};
77
+ });
78
+
79
+ after(() => {
80
+ window.matchMedia = oldMatchMedia;
81
+ window.ontouchstart = oldOnTouchStart;
82
+ });
83
+
84
+ it('should initially provide empty template', async () => {
85
+ const host = await fixture<HostElement>(
86
+ html`<host-element></host-element>`,
87
+ );
88
+ expect(host.controller?.getTemplate()).to.equal(nothing);
89
+ });
90
+
91
+ it('should produce a hover pane template after mousemove, and hide it after mouseleave', async () => {
92
+ const host = await fixture<HostElement>(
93
+ html`<host-element
94
+ .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
95
+ ></host-element>`,
96
+ );
97
+
98
+ host.dispatchEvent(new MouseEvent('mousemove'));
99
+ // Need to wait a tick for the event handlers to run
100
+ await new Promise(resolve => {
101
+ setTimeout(resolve, 0);
102
+ });
103
+
104
+ expect(host.controller?.getTemplate()).not.to.equal(nothing); // Is a TemplateResult
105
+
106
+ host.dispatchEvent(new MouseEvent('mouseleave'));
107
+ // Need to wait for the fade out transition
108
+ await new Promise(resolve => {
109
+ setTimeout(resolve, 150);
110
+ });
111
+
112
+ expect(host.controller?.getTemplate()).to.equal(nothing);
113
+ });
114
+
115
+ it('should produce a hover pane template after mouseenter, even without mousemove', async () => {
116
+ const host = await fixture<HostElement>(
117
+ html`<host-element
118
+ .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
119
+ ></host-element>`,
120
+ );
121
+
122
+ host.dispatchEvent(new MouseEvent('mouseenter'));
123
+ // Need to wait a tick for the event handlers to run
124
+ await new Promise(resolve => {
125
+ setTimeout(resolve, 0);
126
+ });
127
+
128
+ expect(host.controller?.getTemplate()).not.to.equal(nothing); // Is a TemplateResult
129
+ });
130
+
131
+ it('should immediately fade back in if mouse enters while fading out', async () => {
132
+ const host = await fixture<HostElement>(
133
+ html`<host-element
134
+ .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
135
+ ></host-element>`,
136
+ );
137
+
138
+ // Enter the host element and wait for the show handlers to run
139
+ host.dispatchEvent(new MouseEvent('mousemove'));
140
+ await new Promise(resolve => {
141
+ setTimeout(resolve, 0);
142
+ });
143
+
144
+ // Leave the host element so it begins fading out, but not all the way
145
+ host.dispatchEvent(new MouseEvent('mouseleave'));
146
+ await new Promise(resolve => {
147
+ setTimeout(resolve, 20);
148
+ });
149
+
150
+ // Re-enter the host element and wait long enough that it would disappear
151
+ // if the hide were not cancelled
152
+ host.dispatchEvent(new MouseEvent('mousemove'));
153
+ await new Promise(resolve => {
154
+ setTimeout(resolve, 150);
155
+ });
156
+
157
+ expect(host.controller?.getTemplate()).not.to.equal(nothing);
158
+ });
159
+
160
+ it('should flip hover pane if it would overflow the viewport', async () => {
161
+ const host = await fixture<HostElement>(
162
+ html`<host-element
163
+ .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
164
+ ></host-element>`,
165
+ );
166
+
167
+ host.dispatchEvent(
168
+ new MouseEvent('mousemove', { clientX: 800, clientY: 600 }),
169
+ );
170
+ // Need to wait a tick for the event handlers to run
171
+ await new Promise(resolve => {
172
+ setTimeout(resolve, 0);
173
+ });
174
+ await host.updateComplete;
175
+
176
+ expect(host.controller?.getTemplate()).not.to.equal(nothing);
177
+ expect(host.getHoverPane()?.getBoundingClientRect()?.right).to.be.lessThan(
178
+ window.innerWidth,
179
+ );
180
+ expect(host.getHoverPane()?.getBoundingClientRect()?.bottom).to.be.lessThan(
181
+ window.innerHeight,
182
+ );
183
+ });
184
+
185
+ it('should gracefully handle undefined hover pane from host element', async () => {
186
+ const host = await fixture<HostElement>(
187
+ html`<host-element
188
+ .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
189
+ ?suppressHoverPane=${true}
190
+ ></host-element>`,
191
+ );
192
+
193
+ host.dispatchEvent(new MouseEvent('mousemove'));
194
+ // Need to wait a tick for the event handlers to run
195
+ await new Promise(resolve => {
196
+ setTimeout(resolve, 0);
197
+ });
198
+
199
+ expect(host.controller?.getTemplate()).not.to.equal(nothing);
200
+
201
+ host.dispatchEvent(new MouseEvent('mouseleave'));
202
+ await new Promise(resolve => {
203
+ setTimeout(resolve, 20);
204
+ });
205
+
206
+ host.dispatchEvent(new MouseEvent('mousemove'));
207
+ await new Promise(resolve => {
208
+ setTimeout(resolve, 0);
209
+ });
210
+
211
+ host.dispatchEvent(new MouseEvent('mouseleave'));
212
+ // Need to wait for the fade out transition
213
+ await new Promise(resolve => {
214
+ setTimeout(resolve, 150);
215
+ });
216
+
217
+ expect(host.controller?.getTemplate()).to.equal(nothing);
218
+ });
219
+
220
+ describe('Touch & long-press', () => {
221
+ const getTouchStartEvent = (host: EventTarget) =>
222
+ new TouchEvent('touchstart', {
223
+ touches: [new Touch({ identifier: 0, target: host })],
224
+ });
225
+
226
+ it('should produce a hover pane after long press', async () => {
227
+ const host = await fixture<HostElement>(
228
+ html`<host-element
229
+ .controllerOptions=${{
230
+ showDelay: 0,
231
+ longPressDelay: 0,
232
+ enableLongPress: true,
233
+ }}
234
+ ></host-element>`,
235
+ );
236
+
237
+ // Touch the host element and wait for the long press handlers to run
238
+ host.dispatchEvent(getTouchStartEvent(host));
239
+ await new Promise(resolve => {
240
+ setTimeout(resolve, 0);
241
+ });
242
+
243
+ expect(host.controller?.getTemplate()).not.to.equal(nothing); // Is a TemplateResult
244
+ });
245
+
246
+ it('should cancel a long press by moving', async () => {
247
+ const host = await fixture<HostElement>(
248
+ html`<host-element
249
+ .controllerOptions=${{
250
+ showDelay: 0,
251
+ longPressDelay: 100,
252
+ enableLongPress: true,
253
+ }}
254
+ ></host-element>`,
255
+ );
256
+
257
+ // Touch the host element
258
+ host.dispatchEvent(getTouchStartEvent(host));
259
+ await new Promise(resolve => {
260
+ setTimeout(resolve, 0);
261
+ });
262
+
263
+ // Move the touch point, cancelling the long press
264
+ host.dispatchEvent(new TouchEvent('touchmove'));
265
+ await new Promise(resolve => {
266
+ setTimeout(resolve, 150);
267
+ });
268
+
269
+ expect(host.controller?.getTemplate()).to.equal(nothing);
270
+ });
271
+
272
+ it('should cancel a long press by ending touch', async () => {
273
+ const host = await fixture<HostElement>(
274
+ html`<host-element
275
+ .controllerOptions=${{
276
+ showDelay: 0,
277
+ longPressDelay: 100,
278
+ enableLongPress: true,
279
+ }}
280
+ ></host-element>`,
281
+ );
282
+
283
+ // Touch the host element
284
+ host.dispatchEvent(getTouchStartEvent(host));
285
+ await new Promise(resolve => {
286
+ setTimeout(resolve, 0);
287
+ });
288
+
289
+ // Lift the touch point, cancelling the long press
290
+ host.dispatchEvent(new TouchEvent('touchend'));
291
+ await new Promise(resolve => {
292
+ setTimeout(resolve, 150);
293
+ });
294
+
295
+ expect(host.controller?.getTemplate()).to.equal(nothing);
296
+ });
297
+
298
+ it('should cancel a long press by cancelling touch (e.g., too many touch points)', async () => {
299
+ const host = await fixture<HostElement>(
300
+ html`<host-element
301
+ .controllerOptions=${{
302
+ showDelay: 0,
303
+ longPressDelay: 100,
304
+ enableLongPress: true,
305
+ }}
306
+ ></host-element>`,
307
+ );
308
+
309
+ // Touch the host element
310
+ host.dispatchEvent(getTouchStartEvent(host));
311
+ await new Promise(resolve => {
312
+ setTimeout(resolve, 0);
313
+ });
314
+
315
+ // Cancel the touch point, also cancelling the long press
316
+ host.dispatchEvent(new TouchEvent('touchcancel'));
317
+ await new Promise(resolve => {
318
+ setTimeout(resolve, 150);
319
+ });
320
+
321
+ expect(host.controller?.getTemplate()).to.equal(nothing);
322
+ });
323
+
324
+ it('should close the hover pane on mobile when touching the backdrop', async () => {
325
+ const host = await fixture<HostElement>(
326
+ html`<host-element
327
+ .controllerOptions=${{
328
+ showDelay: 0,
329
+ hideDelay: 0,
330
+ longPressDelay: 0,
331
+ enableLongPress: true,
332
+ mobileBreakpoint: 9999, // Ensure we get the mobile view
333
+ }}
334
+ ></host-element>`,
335
+ );
336
+
337
+ // Touch the host element
338
+ host.dispatchEvent(getTouchStartEvent(host));
339
+ await new Promise(resolve => {
340
+ setTimeout(resolve, 0);
341
+ });
342
+
343
+ expect(host.controller?.getTemplate()).not.to.equal(nothing);
344
+
345
+ await host.updateComplete;
346
+
347
+ // Touch the backdrop
348
+ host.shadowRoot
349
+ ?.querySelector('#touch-backdrop')
350
+ ?.dispatchEvent(new TouchEvent('touchstart'));
351
+ await new Promise(resolve => {
352
+ setTimeout(resolve, 150);
353
+ });
354
+
355
+ expect(host.controller?.getTemplate()).to.equal(nothing);
356
+ });
357
+ });
358
+
359
+ describe('keyboard accessibility', () => {
360
+ it('should call host getBoundingClientRect if anchor is host', async () => {
361
+ const host = await fixture<HostElement>(
362
+ html`<host-element
363
+ .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
364
+ ></host-element>`,
365
+ );
366
+
367
+ const getBoundingClientRectSpy = sinon.spy(host, 'getBoundingClientRect');
368
+
369
+ host.dispatchEvent(new FocusEvent('focus'));
370
+ // Need to wait a tick for the event handlers to run
371
+ await new Promise(resolve => {
372
+ setTimeout(resolve, 0);
373
+ });
374
+
375
+ expect(getBoundingClientRectSpy.called).to.be.true;
376
+ });
377
+
378
+ it('should show hover pane on focus', async () => {
379
+ const host = await fixture<HostElement>(
380
+ html`<host-element
381
+ .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
382
+ ></host-element>`,
383
+ );
384
+
385
+ host.dispatchEvent(new FocusEvent('focus'));
386
+ // Need to wait a tick for the event handlers to run
387
+ await new Promise(resolve => {
388
+ setTimeout(resolve, 0);
389
+ });
390
+
391
+ expect(host.controller?.getTemplate()).not.to.equal(nothing); // Is a TemplateResult
392
+ });
393
+
394
+ it('should hide hover pane on blur', async () => {
395
+ const host = await fixture<HostElement>(
396
+ html`<host-element
397
+ .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
398
+ ></host-element>`,
399
+ );
400
+
401
+ host.dispatchEvent(new FocusEvent('focus'));
402
+ // Need to wait a tick for the event handlers to run
403
+ await new Promise(resolve => {
404
+ setTimeout(resolve, 0);
405
+ });
406
+
407
+ expect(host.controller?.getTemplate()).not.to.equal(nothing); // Is a TemplateResult
408
+
409
+ host.dispatchEvent(new FocusEvent('blur'));
410
+ // Need to wait for the fade out transition
411
+ await new Promise(resolve => {
412
+ setTimeout(resolve, 150);
413
+ });
414
+
415
+ expect(host.controller?.getTemplate()).to.equal(nothing);
416
+ });
417
+ });
418
+ });