@internetarchive/collection-browser 1.13.0 → 1.14.0

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 (43) hide show
  1. package/dist/src/app-root.d.ts +10 -0
  2. package/dist/src/app-root.js +27 -0
  3. package/dist/src/app-root.js.map +1 -1
  4. package/dist/src/collection-browser.d.ts +51 -1
  5. package/dist/src/collection-browser.js +172 -14
  6. package/dist/src/collection-browser.js.map +1 -1
  7. package/dist/src/collection-facets.d.ts +1 -0
  8. package/dist/src/collection-facets.js +14 -1
  9. package/dist/src/collection-facets.js.map +1 -1
  10. package/dist/src/manage/manage-bar.d.ts +26 -0
  11. package/dist/src/manage/manage-bar.js +147 -0
  12. package/dist/src/manage/manage-bar.js.map +1 -0
  13. package/dist/src/models.d.ts +1 -0
  14. package/dist/src/models.js.map +1 -1
  15. package/dist/src/tiles/tile-dispatcher.d.ts +13 -0
  16. package/dist/src/tiles/tile-dispatcher.js +70 -2
  17. package/dist/src/tiles/tile-dispatcher.js.map +1 -1
  18. package/dist/test/collection-browser.test.js +144 -1
  19. package/dist/test/collection-browser.test.js.map +1 -1
  20. package/dist/test/item-image.test.js +2 -0
  21. package/dist/test/item-image.test.js.map +1 -1
  22. package/dist/test/manage/manage-bar.test.d.ts +1 -0
  23. package/dist/test/manage/manage-bar.test.js +73 -0
  24. package/dist/test/manage/manage-bar.test.js.map +1 -0
  25. package/dist/test/mocks/mock-search-responses.js +2 -0
  26. package/dist/test/mocks/mock-search-responses.js.map +1 -1
  27. package/dist/test/tiles/hover/hover-pane-controller.test.js +1 -0
  28. package/dist/test/tiles/hover/hover-pane-controller.test.js.map +1 -1
  29. package/dist/test/tiles/tile-dispatcher.test.js +40 -0
  30. package/dist/test/tiles/tile-dispatcher.test.js.map +1 -1
  31. package/package.json +2 -2
  32. package/src/app-root.ts +29 -0
  33. package/src/collection-browser.ts +197 -10
  34. package/src/collection-facets.ts +13 -1
  35. package/src/manage/manage-bar.ts +151 -0
  36. package/src/models.ts +1 -0
  37. package/src/tiles/tile-dispatcher.ts +71 -5
  38. package/test/collection-browser.test.ts +198 -1
  39. package/test/item-image.test.ts +2 -0
  40. package/test/manage/manage-bar.test.ts +107 -0
  41. package/test/mocks/mock-search-responses.ts +2 -0
  42. package/test/tiles/hover/hover-pane-controller.test.ts +1 -0
  43. package/test/tiles/tile-dispatcher.test.ts +52 -0
@@ -1211,7 +1211,8 @@ describe('Collection Browser', () => {
1211
1211
  await el.updateComplete;
1212
1212
 
1213
1213
  const infiniteScroller = el.shadowRoot?.querySelector('infinite-scroller');
1214
- (infiniteScroller as InfiniteScroller).reload = infiniteScrollerRefreshSpy;
1214
+ (infiniteScroller as InfiniteScroller).refreshAllVisibleCells =
1215
+ infiniteScrollerRefreshSpy;
1215
1216
  expect(infiniteScrollerRefreshSpy.called).to.be.false;
1216
1217
  expect(infiniteScrollerRefreshSpy.callCount).to.equal(0);
1217
1218
 
@@ -1462,4 +1463,200 @@ describe('Collection Browser', () => {
1462
1463
  expect(el.minSelectedDate).not.to.exist;
1463
1464
  expect(el.maxSelectedDate).not.to.exist;
1464
1465
  });
1466
+
1467
+ it('shows manage bar interface instead of sort bar when in manage view', async () => {
1468
+ const searchService = new MockSearchService();
1469
+ const el = await fixture<CollectionBrowser>(
1470
+ html`<collection-browser .searchService=${searchService}>
1471
+ </collection-browser>`
1472
+ );
1473
+
1474
+ el.baseQuery = 'foo';
1475
+ await el.updateComplete;
1476
+ await el.initialSearchComplete;
1477
+
1478
+ el.isManageView = true;
1479
+ await el.updateComplete;
1480
+
1481
+ expect(el.shadowRoot?.querySelector('manage-bar')).to.exist;
1482
+ expect(el.shadowRoot?.querySelector('sort-filter-bar')).not.to.exist;
1483
+
1484
+ el.isManageView = false;
1485
+ await el.updateComplete;
1486
+
1487
+ expect(el.shadowRoot?.querySelector('manage-bar')).not.to.exist;
1488
+ expect(el.shadowRoot?.querySelector('sort-filter-bar')).to.exist;
1489
+ });
1490
+
1491
+ it('switches to grid display mode when manage view activated', async () => {
1492
+ const searchService = new MockSearchService();
1493
+ const el = await fixture<CollectionBrowser>(
1494
+ html`<collection-browser
1495
+ .searchService=${searchService}
1496
+ .baseQuery=${'foo'}
1497
+ .displayMode=${'list-detail'}
1498
+ >
1499
+ </collection-browser>`
1500
+ );
1501
+
1502
+ el.isManageView = true;
1503
+ await el.updateComplete;
1504
+
1505
+ expect(el.displayMode).to.equal('grid');
1506
+ });
1507
+
1508
+ it('can remove all checked tiles', async () => {
1509
+ const searchService = new MockSearchService();
1510
+ const el = await fixture<CollectionBrowser>(
1511
+ html`<collection-browser
1512
+ .searchService=${searchService}
1513
+ .baseNavigationUrl=${''}
1514
+ >
1515
+ </collection-browser>`
1516
+ );
1517
+
1518
+ el.baseQuery = 'foo';
1519
+ el.pageSize = 1; // To hit the edge case of a page break while offsetting tiles
1520
+ await el.updateComplete;
1521
+ await el.initialSearchComplete;
1522
+
1523
+ el.isManageView = true;
1524
+ await el.updateComplete;
1525
+
1526
+ const infiniteScroller = el.shadowRoot?.querySelector('infinite-scroller');
1527
+ expect(infiniteScroller).to.exist;
1528
+
1529
+ let tiles =
1530
+ infiniteScroller!.shadowRoot?.querySelectorAll('tile-dispatcher');
1531
+ expect(tiles).to.exist.and.have.length(2);
1532
+
1533
+ const firstTile = tiles![0] as TileDispatcher;
1534
+ const firstTileLink = firstTile.shadowRoot?.querySelector(
1535
+ 'a[href]'
1536
+ ) as HTMLAnchorElement;
1537
+ expect(firstTile.model?.identifier).to.equal('foo');
1538
+ expect(firstTileLink).to.exist;
1539
+
1540
+ // No effect if no tiles checked
1541
+ el.removeCheckedTiles();
1542
+ await el.updateComplete;
1543
+ tiles = infiniteScroller!.shadowRoot?.querySelectorAll('tile-dispatcher');
1544
+ expect(tiles).to.exist.and.have.length(2);
1545
+
1546
+ // Check the first tile
1547
+ firstTileLink!.click();
1548
+ expect(firstTile.model?.checked).to.be.true;
1549
+
1550
+ // Remove checked tiles and verify that we only kept the second tile
1551
+ el.removeCheckedTiles();
1552
+ await el.updateComplete;
1553
+ tiles = infiniteScroller!.shadowRoot?.querySelectorAll('tile-dispatcher');
1554
+ expect(tiles).to.exist.and.have.length(1);
1555
+ expect((tiles![0] as TileDispatcher).model?.identifier).to.equal('bar');
1556
+ });
1557
+
1558
+ it('can check/uncheck all tiles', async () => {
1559
+ const searchService = new MockSearchService();
1560
+ const el = await fixture<CollectionBrowser>(
1561
+ html`<collection-browser
1562
+ .searchService=${searchService}
1563
+ .baseNavigationUrl=${''}
1564
+ >
1565
+ </collection-browser>`
1566
+ );
1567
+
1568
+ el.baseQuery = 'foo';
1569
+ await el.updateComplete;
1570
+ await el.initialSearchComplete;
1571
+
1572
+ el.isManageView = true;
1573
+ await el.updateComplete;
1574
+
1575
+ expect(el.checkedTileModels.length).to.equal(0);
1576
+ expect(el.uncheckedTileModels.length).to.equal(2);
1577
+
1578
+ el.checkAllTiles();
1579
+ expect(el.checkedTileModels.length).to.equal(2);
1580
+ expect(el.uncheckedTileModels.length).to.equal(0);
1581
+
1582
+ el.uncheckAllTiles();
1583
+ expect(el.checkedTileModels.length).to.equal(0);
1584
+ expect(el.uncheckedTileModels.length).to.equal(2);
1585
+ });
1586
+
1587
+ it('emits event when item removal requested', async () => {
1588
+ const spy = sinon.spy();
1589
+ const searchService = new MockSearchService();
1590
+ const el = await fixture<CollectionBrowser>(
1591
+ html`<collection-browser
1592
+ .searchService=${searchService}
1593
+ .baseNavigationUrl=${''}
1594
+ @itemRemovalRequested=${spy}
1595
+ >
1596
+ </collection-browser>`
1597
+ );
1598
+
1599
+ el.baseQuery = 'foo';
1600
+ await el.updateComplete;
1601
+ await el.initialSearchComplete;
1602
+
1603
+ el.isManageView = true;
1604
+ await el.updateComplete;
1605
+
1606
+ const infiniteScroller = el.shadowRoot?.querySelector('infinite-scroller');
1607
+ expect(infiniteScroller).to.exist;
1608
+
1609
+ const tiles =
1610
+ infiniteScroller!.shadowRoot?.querySelectorAll('tile-dispatcher');
1611
+ expect(tiles).to.exist.and.have.length(2);
1612
+
1613
+ const firstTile = tiles![0] as TileDispatcher;
1614
+ const firstTileLink = firstTile.shadowRoot?.querySelector(
1615
+ 'a[href]'
1616
+ ) as HTMLAnchorElement;
1617
+ expect(firstTile.model?.identifier).to.equal('foo');
1618
+ expect(firstTileLink).to.exist;
1619
+
1620
+ // Check the first tile
1621
+ firstTileLink!.click();
1622
+ await el.updateComplete;
1623
+
1624
+ const manageBar = el.shadowRoot?.querySelector('manage-bar');
1625
+ expect(manageBar).to.exist;
1626
+
1627
+ // Emit remove event from manage bar
1628
+ manageBar!.dispatchEvent(new CustomEvent('removeItems'));
1629
+
1630
+ await el.updateComplete;
1631
+ expect(spy.callCount).to.equal(1);
1632
+ expect(spy.args[0].length).to.equal(1);
1633
+ expect(spy.args[0][0]?.detail?.items?.[0]?.identifier).to.equal('foo');
1634
+ });
1635
+
1636
+ it('disables manage view when manage bar cancelled', async () => {
1637
+ const searchService = new MockSearchService();
1638
+ const el = await fixture<CollectionBrowser>(
1639
+ html`<collection-browser
1640
+ .searchService=${searchService}
1641
+ .baseNavigationUrl=${''}
1642
+ >
1643
+ </collection-browser>`
1644
+ );
1645
+
1646
+ el.baseQuery = 'foo';
1647
+ await el.updateComplete;
1648
+ await el.initialSearchComplete;
1649
+
1650
+ el.isManageView = true;
1651
+ await el.updateComplete;
1652
+
1653
+ const manageBar = el.shadowRoot?.querySelector('manage-bar');
1654
+ expect(manageBar).to.exist;
1655
+
1656
+ // Emit remove event from manage bar
1657
+ manageBar!.dispatchEvent(new CustomEvent('cancel'));
1658
+
1659
+ await el.updateComplete;
1660
+ expect(el.isManageView).to.be.false;
1661
+ });
1465
1662
  });
@@ -9,6 +9,7 @@ import '../src/tiles/item-image';
9
9
 
10
10
  const baseImageUrl = 'https://archive.org';
11
11
  const testBookModel: TileModel = {
12
+ checked: false,
12
13
  collections: [],
13
14
  commentCount: 0,
14
15
  creators: [],
@@ -26,6 +27,7 @@ const testBookModel: TileModel = {
26
27
  };
27
28
 
28
29
  const testAudioModel: TileModel = {
30
+ checked: false,
29
31
  collections: [],
30
32
  commentCount: 0,
31
33
  creators: [],
@@ -0,0 +1,107 @@
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 { ManageBar } from '../../src/manage/manage-bar';
6
+
7
+ import '../../src/manage/manage-bar';
8
+
9
+ describe('Manage bar', () => {
10
+ it('renders basic component', async () => {
11
+ const el = await fixture<ManageBar>(html`<manage-bar></manage-bar>`);
12
+
13
+ expect(el.shadowRoot?.querySelector('.manage-label')).to.exist;
14
+ expect(el.shadowRoot?.querySelector('.manage-buttons')).to.exist;
15
+ expect(el.shadowRoot?.querySelector('.cancel-btn')).to.exist;
16
+ expect(el.shadowRoot?.querySelector('.remove-btn')).to.exist;
17
+ });
18
+
19
+ it('can set the label', async () => {
20
+ const el = await fixture<ManageBar>(
21
+ html`<manage-bar label="foo bar"></manage-bar>`
22
+ );
23
+ expect(el.shadowRoot?.querySelector('.manage-label')?.textContent).to.equal(
24
+ 'foo bar'
25
+ );
26
+ });
27
+
28
+ it('does not include Select All/Unselect All buttons by default', async () => {
29
+ const el = await fixture<ManageBar>(html`<manage-bar></manage-bar>`);
30
+ expect(el.shadowRoot?.querySelector('.select-all-btn')).not.to.exist;
31
+ expect(el.shadowRoot?.querySelector('.unselect-all-btn')).not.to.exist;
32
+ });
33
+
34
+ it('includes Select All button when requested', async () => {
35
+ const el = await fixture<ManageBar>(
36
+ html`<manage-bar showSelectAll></manage-bar>`
37
+ );
38
+ expect(el.shadowRoot?.querySelector('.select-all-btn')).to.exist;
39
+ });
40
+
41
+ it('includes Unselect All button when requested', async () => {
42
+ const el = await fixture<ManageBar>(
43
+ html`<manage-bar showUnselectAll></manage-bar>`
44
+ );
45
+ expect(el.shadowRoot?.querySelector('.unselect-all-btn')).to.exist;
46
+ });
47
+
48
+ it('emits event when Cancel button clicked', async () => {
49
+ const spy = Sinon.spy();
50
+ const el = await fixture<ManageBar>(
51
+ html`<manage-bar @cancel=${spy}></manage-bar>`
52
+ );
53
+
54
+ const cancelBtn = el.shadowRoot?.querySelector(
55
+ '.cancel-btn'
56
+ ) as HTMLButtonElement;
57
+ expect(cancelBtn).to.exist;
58
+
59
+ cancelBtn.click();
60
+ expect(spy.callCount).to.equal(1);
61
+ });
62
+
63
+ it('emits event when Remove Items button clicked', async () => {
64
+ const spy = Sinon.spy();
65
+ const el = await fixture<ManageBar>(
66
+ html`<manage-bar @removeItems=${spy}></manage-bar>`
67
+ );
68
+
69
+ const removeItemsBtn = el.shadowRoot?.querySelector(
70
+ '.remove-btn'
71
+ ) as HTMLButtonElement;
72
+ expect(removeItemsBtn).to.exist;
73
+
74
+ removeItemsBtn.click();
75
+ expect(spy.callCount).to.equal(1);
76
+ });
77
+
78
+ it('emits event when Select All button clicked', async () => {
79
+ const spy = Sinon.spy();
80
+ const el = await fixture<ManageBar>(
81
+ html`<manage-bar showSelectAll @selectAll=${spy}></manage-bar>`
82
+ );
83
+
84
+ const selectAllBtn = el.shadowRoot?.querySelector(
85
+ '.select-all-btn'
86
+ ) as HTMLButtonElement;
87
+ expect(selectAllBtn).to.exist;
88
+
89
+ selectAllBtn.click();
90
+ expect(spy.callCount).to.equal(1);
91
+ });
92
+
93
+ it('emits event when Unselect All button clicked', async () => {
94
+ const spy = Sinon.spy();
95
+ const el = await fixture<ManageBar>(
96
+ html`<manage-bar showUnselectAll @unselectAll=${spy}></manage-bar>`
97
+ );
98
+
99
+ const unselectAllBtn = el.shadowRoot?.querySelector(
100
+ '.unselect-all-btn'
101
+ ) as HTMLButtonElement;
102
+ expect(unselectAllBtn).to.exist;
103
+
104
+ unselectAllBtn.click();
105
+ expect(spy.callCount).to.equal(1);
106
+ });
107
+ });
@@ -489,12 +489,14 @@ export const getMockSuccessMultipleResults: () => Result<
489
489
  fields: {
490
490
  identifier: 'foo',
491
491
  collection: ['foo', 'bar'],
492
+ __href__: '/foo',
492
493
  },
493
494
  }),
494
495
  new ItemHit({
495
496
  fields: {
496
497
  identifier: 'bar',
497
498
  collection: ['baz', 'boop'],
499
+ __href__: '/bar',
498
500
  },
499
501
  }),
500
502
  ],
@@ -35,6 +35,7 @@ class HostElement extends LitElement implements HoverPaneProviderInterface {
35
35
  getHoverPaneProps(): HoverPaneProperties {
36
36
  return {
37
37
  model: {
38
+ checked: false,
38
39
  collectionFilesCount: 1,
39
40
  collections: ['foo', 'bar'],
40
41
  collectionSize: 1,
@@ -1,5 +1,6 @@
1
1
  import { aTimeout, expect, fixture } from '@open-wc/testing';
2
2
  import { html } from 'lit';
3
+ import sinon from 'sinon';
3
4
  import type { TileDispatcher } from '../../src/tiles/tile-dispatcher';
4
5
 
5
6
  import '../../src/tiles/tile-dispatcher';
@@ -67,6 +68,57 @@ describe('Tile Dispatcher', () => {
67
68
  expect(compactListTile).to.exist;
68
69
  });
69
70
 
71
+ it('should open item in new tab when right-clicked in manage mode', async () => {
72
+ const oldWindowOpen = window.open;
73
+ const spy = sinon.spy();
74
+ window.open = spy;
75
+
76
+ const el = await fixture<TileDispatcher>(html`
77
+ <tile-dispatcher
78
+ isManageView
79
+ .model=${{ identifier: 'foo', href: '/foo' }}
80
+ .baseNavigationUrl=${''}
81
+ >
82
+ </tile-dispatcher>
83
+ `);
84
+
85
+ const tileLink = el.shadowRoot?.querySelector(
86
+ 'a[href]'
87
+ ) as HTMLAnchorElement;
88
+ expect(tileLink).to.exist;
89
+
90
+ tileLink.dispatchEvent(new Event('contextmenu'));
91
+ await el.updateComplete;
92
+
93
+ expect(spy.callCount).to.equal(1);
94
+ expect(spy.args[0][0]).to.equal('/foo');
95
+ expect(spy.args[0][1]).to.equal('_blank');
96
+
97
+ window.open = oldWindowOpen;
98
+ });
99
+
100
+ it('should toggle model checked state when manage check clicked', async () => {
101
+ const el = await fixture<TileDispatcher>(html`
102
+ <tile-dispatcher
103
+ isManageView
104
+ .model=${{ identifier: 'foo', href: '/foo' }}
105
+ .tileDisplayMode=${'grid'}
106
+ ></tile-dispatcher>
107
+ `);
108
+
109
+ const manageCheck = el.shadowRoot?.querySelector(
110
+ '.manage-check > input[type="checkbox"]'
111
+ ) as HTMLButtonElement;
112
+
113
+ manageCheck.click();
114
+ await el.updateComplete;
115
+ expect(el.model?.checked).to.be.true;
116
+
117
+ manageCheck.click();
118
+ await el.updateComplete;
119
+ expect(el.model?.checked).to.be.false;
120
+ });
121
+
70
122
  it('should return hover pane props', async () => {
71
123
  const el = await fixture<TileDispatcher>(html`
72
124
  <tile-dispatcher .model=${{ identifier: 'foo' }}> </tile-dispatcher>