@internetarchive/collection-browser 0.4.4-alpha → 0.4.4

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 (75) hide show
  1. package/dist/src/app-root.js +17 -2
  2. package/dist/src/app-root.js.map +1 -1
  3. package/dist/src/collection-browser.d.ts +2 -40
  4. package/dist/src/collection-browser.js +45 -118
  5. package/dist/src/collection-browser.js.map +1 -1
  6. package/dist/src/sort-filter-bar/sort-filter-bar.js +11 -1
  7. package/dist/src/sort-filter-bar/sort-filter-bar.js.map +1 -1
  8. package/dist/src/styles/item-image-styles.js +5 -1
  9. package/dist/src/styles/item-image-styles.js.map +1 -1
  10. package/dist/src/tiles/grid/item-tile.js +15 -2
  11. package/dist/src/tiles/grid/item-tile.js.map +1 -1
  12. package/dist/src/tiles/grid/styles/tile-grid-shared-styles.js +6 -6
  13. package/dist/src/tiles/grid/styles/tile-grid-shared-styles.js.map +1 -1
  14. package/dist/src/tiles/grid/tile-stats.js +15 -7
  15. package/dist/src/tiles/grid/tile-stats.js.map +1 -1
  16. package/dist/src/tiles/hover/hover-pane-controller.d.ts +197 -0
  17. package/dist/src/tiles/hover/hover-pane-controller.js +349 -0
  18. package/dist/src/tiles/hover/hover-pane-controller.js.map +1 -0
  19. package/dist/src/tiles/hover/tile-hover-pane.d.ts +15 -0
  20. package/dist/src/tiles/hover/tile-hover-pane.js +88 -0
  21. package/dist/src/tiles/hover/tile-hover-pane.js.map +1 -0
  22. package/dist/src/tiles/image-block.js +4 -0
  23. package/dist/src/tiles/image-block.js.map +1 -1
  24. package/dist/src/tiles/list/date-label.js +3 -3
  25. package/dist/src/tiles/list/date-label.js.map +1 -1
  26. package/dist/src/tiles/list/tile-list-compact-header.js +4 -3
  27. package/dist/src/tiles/list/tile-list-compact-header.js.map +1 -1
  28. package/dist/src/tiles/list/tile-list-compact.js +15 -5
  29. package/dist/src/tiles/list/tile-list-compact.js.map +1 -1
  30. package/dist/src/tiles/list/tile-list.d.ts +2 -0
  31. package/dist/src/tiles/list/tile-list.js +63 -5
  32. package/dist/src/tiles/list/tile-list.js.map +1 -1
  33. package/dist/src/tiles/text-snippet-block.js +4 -4
  34. package/dist/src/tiles/text-snippet-block.js.map +1 -1
  35. package/dist/src/tiles/tile-dispatcher.d.ts +21 -2
  36. package/dist/src/tiles/tile-dispatcher.js +79 -9
  37. package/dist/src/tiles/tile-dispatcher.js.map +1 -1
  38. package/dist/test/collection-browser.test.js +6 -72
  39. package/dist/test/collection-browser.test.js.map +1 -1
  40. package/dist/test/sort-filter-bar/sort-filter-bar.test.js +64 -0
  41. package/dist/test/sort-filter-bar/sort-filter-bar.test.js.map +1 -1
  42. package/dist/test/tiles/grid/account-tile.test.js +1 -1
  43. package/dist/test/tiles/grid/account-tile.test.js.map +1 -1
  44. package/dist/test/tiles/hover/hover-pane-controller.test.d.ts +1 -0
  45. package/dist/test/tiles/hover/hover-pane-controller.test.js +279 -0
  46. package/dist/test/tiles/hover/hover-pane-controller.test.js.map +1 -0
  47. package/dist/test/tiles/hover/tile-hover-pane.test.d.ts +1 -0
  48. package/dist/test/tiles/hover/tile-hover-pane.test.js +14 -0
  49. package/dist/test/tiles/hover/tile-hover-pane.test.js.map +1 -0
  50. package/dist/test/tiles/list/tile-list.test.js +46 -1
  51. package/dist/test/tiles/list/tile-list.test.js.map +1 -1
  52. package/index.html +1 -0
  53. package/package.json +2 -2
  54. package/src/app-root.ts +17 -2
  55. package/src/collection-browser.ts +42 -140
  56. package/src/sort-filter-bar/sort-filter-bar.ts +12 -1
  57. package/src/styles/item-image-styles.ts +5 -1
  58. package/src/tiles/grid/item-tile.ts +15 -2
  59. package/src/tiles/grid/styles/tile-grid-shared-styles.ts +6 -6
  60. package/src/tiles/grid/tile-stats.ts +15 -7
  61. package/src/tiles/hover/hover-pane-controller.ts +469 -0
  62. package/src/tiles/hover/tile-hover-pane.ts +79 -0
  63. package/src/tiles/image-block.ts +4 -0
  64. package/src/tiles/list/date-label.ts +3 -3
  65. package/src/tiles/list/tile-list-compact-header.ts +4 -3
  66. package/src/tiles/list/tile-list-compact.ts +15 -5
  67. package/src/tiles/list/tile-list.ts +67 -5
  68. package/src/tiles/text-snippet-block.ts +4 -4
  69. package/src/tiles/tile-dispatcher.ts +95 -7
  70. package/test/collection-browser.test.ts +7 -101
  71. package/test/sort-filter-bar/sort-filter-bar.test.ts +89 -0
  72. package/test/tiles/grid/account-tile.test.ts +1 -1
  73. package/test/tiles/hover/hover-pane-controller.test.ts +349 -0
  74. package/test/tiles/hover/tile-hover-pane.test.ts +19 -0
  75. package/test/tiles/list/tile-list.test.ts +58 -1
@@ -0,0 +1,349 @@
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
+
13
+ @customElement('host-element')
14
+ class HostElement extends LitElement implements HoverPaneProviderInterface {
15
+ @property({ type: Object }) controllerOptions?: HoverPaneControllerOptions;
16
+
17
+ @property({ type: Boolean }) suppressHoverPane: boolean = false;
18
+
19
+ @query('tile-hover-pane') hoverPane?: TileHoverPane;
20
+
21
+ controller?: HoverPaneControllerInterface;
22
+
23
+ render(): TemplateResult {
24
+ return html` ${this.controller?.getTemplate()} `;
25
+ }
26
+
27
+ protected firstUpdated(): void {
28
+ this.controller = new HoverPaneController(this, this.controllerOptions);
29
+ }
30
+
31
+ getHoverPane(): HTMLElement | undefined {
32
+ return this.suppressHoverPane ? undefined : this.hoverPane;
33
+ }
34
+
35
+ getHoverPaneProps(): HoverPaneProperties {
36
+ return {
37
+ model: {
38
+ collectionFilesCount: 1,
39
+ collections: ['foo', 'bar'],
40
+ collectionSize: 1,
41
+ commentCount: 1,
42
+ contentWarning: false,
43
+ creators: ['foo', 'bar'],
44
+ favCount: 1,
45
+ identifier: 'foo',
46
+ itemCount: 1,
47
+ loginRequired: false,
48
+ mediatype: 'data',
49
+ subjects: ['foo', 'bar'],
50
+ title: 'foo',
51
+ viewCount: 1,
52
+ },
53
+ loggedIn: false,
54
+ sortParam: null,
55
+ };
56
+ }
57
+ }
58
+
59
+ describe('Hover Pane Controller', () => {
60
+ let oldMatchMedia: typeof window.matchMedia;
61
+ let oldOnTouchStart: typeof window.ontouchstart;
62
+
63
+ before(() => {
64
+ oldMatchMedia = window.matchMedia;
65
+ oldOnTouchStart = window.ontouchstart;
66
+ window.matchMedia = () => ({ matches: true } as MediaQueryList);
67
+ window.ontouchstart = () => {};
68
+ });
69
+
70
+ after(() => {
71
+ window.matchMedia = oldMatchMedia;
72
+ window.ontouchstart = oldOnTouchStart;
73
+ });
74
+
75
+ it('should initially provide empty template', async () => {
76
+ const host = await fixture<HostElement>(
77
+ html`<host-element></host-element>`
78
+ );
79
+ expect(host.controller?.getTemplate()).to.equal(nothing);
80
+ });
81
+
82
+ it('should produce a hover pane template after mousemove, and hide it after mouseleave', async () => {
83
+ const host = await fixture<HostElement>(
84
+ html`<host-element
85
+ .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
86
+ ></host-element>`
87
+ );
88
+
89
+ host.dispatchEvent(new MouseEvent('mousemove'));
90
+ // Need to wait a tick for the event handlers to run
91
+ await new Promise(resolve => {
92
+ setTimeout(resolve, 0);
93
+ });
94
+
95
+ expect(host.controller?.getTemplate()).not.to.equal(nothing); // Is a TemplateResult
96
+
97
+ host.dispatchEvent(new MouseEvent('mouseleave'));
98
+ // Need to wait for the fade out transition
99
+ await new Promise(resolve => {
100
+ setTimeout(resolve, 150);
101
+ });
102
+
103
+ expect(host.controller?.getTemplate()).to.equal(nothing);
104
+ });
105
+
106
+ it('should produce a hover pane template after mouseenter, even without mousemove', async () => {
107
+ const host = await fixture<HostElement>(
108
+ html`<host-element
109
+ .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
110
+ ></host-element>`
111
+ );
112
+
113
+ host.dispatchEvent(new MouseEvent('mouseenter'));
114
+ // Need to wait a tick for the event handlers to run
115
+ await new Promise(resolve => {
116
+ setTimeout(resolve, 0);
117
+ });
118
+
119
+ expect(host.controller?.getTemplate()).not.to.equal(nothing); // Is a TemplateResult
120
+ });
121
+
122
+ it('should immediately fade back in if mouse enters while fading out', async () => {
123
+ const host = await fixture<HostElement>(
124
+ html`<host-element
125
+ .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
126
+ ></host-element>`
127
+ );
128
+
129
+ // Enter the host element and wait for the show handlers to run
130
+ host.dispatchEvent(new MouseEvent('mousemove'));
131
+ await new Promise(resolve => {
132
+ setTimeout(resolve, 0);
133
+ });
134
+
135
+ // Leave the host element so it begins fading out, but not all the way
136
+ host.dispatchEvent(new MouseEvent('mouseleave'));
137
+ await new Promise(resolve => {
138
+ setTimeout(resolve, 20);
139
+ });
140
+
141
+ // Re-enter the host element and wait long enough that it would disappear
142
+ // if the hide were not cancelled
143
+ host.dispatchEvent(new MouseEvent('mousemove'));
144
+ await new Promise(resolve => {
145
+ setTimeout(resolve, 150);
146
+ });
147
+
148
+ expect(host.controller?.getTemplate()).not.to.equal(nothing);
149
+ });
150
+
151
+ it('should flip hover pane if it would overflow the viewport', async () => {
152
+ const host = await fixture<HostElement>(
153
+ html`<host-element
154
+ .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
155
+ ></host-element>`
156
+ );
157
+
158
+ host.dispatchEvent(
159
+ new MouseEvent('mousemove', { clientX: 800, clientY: 600 })
160
+ );
161
+ // Need to wait a tick for the event handlers to run
162
+ await new Promise(resolve => {
163
+ setTimeout(resolve, 0);
164
+ });
165
+ await host.updateComplete;
166
+
167
+ expect(host.controller?.getTemplate()).not.to.equal(nothing);
168
+ expect(host.getHoverPane()?.getBoundingClientRect()?.right).to.be.lessThan(
169
+ window.innerWidth
170
+ );
171
+ expect(host.getHoverPane()?.getBoundingClientRect()?.bottom).to.be.lessThan(
172
+ window.innerHeight
173
+ );
174
+ });
175
+
176
+ it('should gracefully handle undefined hover pane from host element', async () => {
177
+ const host = await fixture<HostElement>(
178
+ html`<host-element
179
+ .controllerOptions=${{ showDelay: 0, hideDelay: 0 }}
180
+ ?suppressHoverPane=${true}
181
+ ></host-element>`
182
+ );
183
+
184
+ host.dispatchEvent(new MouseEvent('mousemove'));
185
+ // Need to wait a tick for the event handlers to run
186
+ await new Promise(resolve => {
187
+ setTimeout(resolve, 0);
188
+ });
189
+
190
+ expect(host.controller?.getTemplate()).not.to.equal(nothing);
191
+
192
+ host.dispatchEvent(new MouseEvent('mouseleave'));
193
+ await new Promise(resolve => {
194
+ setTimeout(resolve, 20);
195
+ });
196
+
197
+ host.dispatchEvent(new MouseEvent('mousemove'));
198
+ await new Promise(resolve => {
199
+ setTimeout(resolve, 0);
200
+ });
201
+
202
+ host.dispatchEvent(new MouseEvent('mouseleave'));
203
+ // Need to wait for the fade out transition
204
+ await new Promise(resolve => {
205
+ setTimeout(resolve, 150);
206
+ });
207
+
208
+ expect(host.controller?.getTemplate()).to.equal(nothing);
209
+ });
210
+
211
+ describe('Touch & long-press', () => {
212
+ const getTouchStartEvent = (host: EventTarget) =>
213
+ new TouchEvent('touchstart', {
214
+ touches: [new Touch({ identifier: 0, target: host })],
215
+ });
216
+
217
+ it('should produce a hover pane after long press', async () => {
218
+ const host = await fixture<HostElement>(
219
+ html`<host-element
220
+ .controllerOptions=${{
221
+ showDelay: 0,
222
+ longPressDelay: 0,
223
+ enableLongPress: true,
224
+ }}
225
+ ></host-element>`
226
+ );
227
+
228
+ // Touch the host element and wait for the long press handlers to run
229
+ host.dispatchEvent(getTouchStartEvent(host));
230
+ await new Promise(resolve => {
231
+ setTimeout(resolve, 0);
232
+ });
233
+
234
+ expect(host.controller?.getTemplate()).not.to.equal(nothing); // Is a TemplateResult
235
+ });
236
+
237
+ it('should cancel a long press by moving', async () => {
238
+ const host = await fixture<HostElement>(
239
+ html`<host-element
240
+ .controllerOptions=${{
241
+ showDelay: 0,
242
+ longPressDelay: 100,
243
+ enableLongPress: true,
244
+ }}
245
+ ></host-element>`
246
+ );
247
+
248
+ // Touch the host element
249
+ host.dispatchEvent(getTouchStartEvent(host));
250
+ await new Promise(resolve => {
251
+ setTimeout(resolve, 0);
252
+ });
253
+
254
+ // Move the touch point, cancelling the long press
255
+ host.dispatchEvent(new TouchEvent('touchmove'));
256
+ await new Promise(resolve => {
257
+ setTimeout(resolve, 150);
258
+ });
259
+
260
+ expect(host.controller?.getTemplate()).to.equal(nothing);
261
+ });
262
+
263
+ it('should cancel a long press by ending touch', async () => {
264
+ const host = await fixture<HostElement>(
265
+ html`<host-element
266
+ .controllerOptions=${{
267
+ showDelay: 0,
268
+ longPressDelay: 100,
269
+ enableLongPress: true,
270
+ }}
271
+ ></host-element>`
272
+ );
273
+
274
+ // Touch the host element
275
+ host.dispatchEvent(getTouchStartEvent(host));
276
+ await new Promise(resolve => {
277
+ setTimeout(resolve, 0);
278
+ });
279
+
280
+ // Lift the touch point, cancelling the long press
281
+ host.dispatchEvent(new TouchEvent('touchend'));
282
+ await new Promise(resolve => {
283
+ setTimeout(resolve, 150);
284
+ });
285
+
286
+ expect(host.controller?.getTemplate()).to.equal(nothing);
287
+ });
288
+
289
+ it('should cancel a long press by cancelling touch (e.g., too many touch points)', async () => {
290
+ const host = await fixture<HostElement>(
291
+ html`<host-element
292
+ .controllerOptions=${{
293
+ showDelay: 0,
294
+ longPressDelay: 100,
295
+ enableLongPress: true,
296
+ }}
297
+ ></host-element>`
298
+ );
299
+
300
+ // Touch the host element
301
+ host.dispatchEvent(getTouchStartEvent(host));
302
+ await new Promise(resolve => {
303
+ setTimeout(resolve, 0);
304
+ });
305
+
306
+ // Cancel the touch point, also cancelling the long press
307
+ host.dispatchEvent(new TouchEvent('touchcancel'));
308
+ await new Promise(resolve => {
309
+ setTimeout(resolve, 150);
310
+ });
311
+
312
+ expect(host.controller?.getTemplate()).to.equal(nothing);
313
+ });
314
+
315
+ it('should close the hover pane on mobile when touching the backdrop', async () => {
316
+ const host = await fixture<HostElement>(
317
+ html`<host-element
318
+ .controllerOptions=${{
319
+ showDelay: 0,
320
+ hideDelay: 0,
321
+ longPressDelay: 0,
322
+ enableLongPress: true,
323
+ mobileBreakpoint: 9999, // Ensure we get the mobile view
324
+ }}
325
+ ></host-element>`
326
+ );
327
+
328
+ // Touch the host element
329
+ host.dispatchEvent(getTouchStartEvent(host));
330
+ await new Promise(resolve => {
331
+ setTimeout(resolve, 0);
332
+ });
333
+
334
+ expect(host.controller?.getTemplate()).not.to.equal(nothing);
335
+
336
+ await host.updateComplete;
337
+
338
+ // Touch the backdrop
339
+ host.shadowRoot
340
+ ?.querySelector('#touch-backdrop')
341
+ ?.dispatchEvent(new TouchEvent('touchstart'));
342
+ await new Promise(resolve => {
343
+ setTimeout(resolve, 150);
344
+ });
345
+
346
+ expect(host.controller?.getTemplate()).to.equal(nothing);
347
+ });
348
+ });
349
+ });
@@ -0,0 +1,19 @@
1
+ import { expect, fixture } from '@open-wc/testing';
2
+ import { html } from 'lit';
3
+ import type { TileHoverPane } from '../../../src/tiles/hover/tile-hover-pane';
4
+
5
+ import '../../../src/tiles/hover/tile-hover-pane';
6
+
7
+ describe('Tile Hover Pane', () => {
8
+ it('should render initial component', async () => {
9
+ const el = await fixture<TileHoverPane>(
10
+ html`<tile-hover-pane></tile-hover-pane>`
11
+ );
12
+
13
+ const container = el.shadowRoot?.querySelector('#container');
14
+ const listView = el.shadowRoot?.querySelector('tile-list');
15
+
16
+ expect(container).to.exist;
17
+ expect(listView).to.exist;
18
+ });
19
+ });
@@ -9,7 +9,9 @@ import type { TileModel } from '../../../src/models';
9
9
 
10
10
  describe('List Tile', () => {
11
11
  it('should render initial component', async () => {
12
- const el = await fixture<TileList>(html`<tile-list></tile-list>`);
12
+ const el = await fixture<TileList>(
13
+ html`<tile-list .model=${{}}></tile-list>`
14
+ );
13
15
 
14
16
  const listContainer = el.shadowRoot?.querySelector('#list-line');
15
17
  const itemTitle = el.shadowRoot?.querySelector('#title');
@@ -181,6 +183,61 @@ describe('List Tile', () => {
181
183
  expect(descriptionBlock?.textContent?.trim()).to.equal('line1 line2'); // line break replaced by space
182
184
  });
183
185
 
186
+ it('should render mediatype icon as link to corresponding mediatype collection details', async () => {
187
+ const model: Partial<TileModel> = {
188
+ mediatype: 'texts',
189
+ };
190
+
191
+ const el = await fixture<TileList>(html`
192
+ <tile-list
193
+ .baseNavigationUrl=${'https://archive.org'}
194
+ .model=${model}
195
+ ></tile-list>
196
+ `);
197
+
198
+ const mediatypeLink = el.shadowRoot?.querySelector('a#icon-right');
199
+ expect(mediatypeLink).to.exist;
200
+ expect(mediatypeLink?.getAttribute('href')).to.equal(
201
+ `https://archive.org/details/texts`
202
+ );
203
+ });
204
+
205
+ it('should render collection mediatype icon as link to search page', async () => {
206
+ const model: Partial<TileModel> = {
207
+ mediatype: 'collection',
208
+ };
209
+
210
+ const el = await fixture<TileList>(html`
211
+ <tile-list
212
+ .baseNavigationUrl=${'https://archive.org'}
213
+ .model=${model}
214
+ ></tile-list>
215
+ `);
216
+
217
+ const mediatypeLink = el.shadowRoot?.querySelector('a#icon-right');
218
+ expect(mediatypeLink).to.exist;
219
+ expect(mediatypeLink?.getAttribute('href')).to.equal(
220
+ `https://archive.org/search?query=mediatype:collection&sort=-downloads`
221
+ );
222
+ });
223
+
224
+ it('should not render account mediatype icon as link', async () => {
225
+ const model: Partial<TileModel> = {
226
+ mediatype: 'account',
227
+ };
228
+
229
+ const el = await fixture<TileList>(html`
230
+ <tile-list
231
+ .baseNavigationUrl=${'https://archive.org'}
232
+ .model=${model}
233
+ ></tile-list>
234
+ `);
235
+
236
+ const mediatypeLink = el.shadowRoot?.querySelector('a#icon-right');
237
+ expect(mediatypeLink).to.exist;
238
+ expect(mediatypeLink?.getAttribute('href')).not.to.exist;
239
+ });
240
+
184
241
  it('should render date added for accounts', async () => {
185
242
  const el = await fixture<TileList>(html`
186
243
  <tile-list