@internetarchive/bookreader 5.0.0-70-a3 → 5.0.0-70

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. package/BookReader/BookReader.js +1 -1
  2. package/BookReader/BookReader.js.map +1 -1
  3. package/BookReader/ia-bookreader-bundle.js +293 -586
  4. package/BookReader/ia-bookreader-bundle.js.map +1 -1
  5. package/BookReader/plugins/plugin.chapters.js +2 -2
  6. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  7. package/BookReader/plugins/plugin.tts.js +1 -1
  8. package/BookReader/plugins/plugin.tts.js.map +1 -1
  9. package/BookReader/plugins/plugin.url.js +1 -1
  10. package/BookReader/plugins/plugin.url.js.map +1 -1
  11. package/BookReaderDemo/demo-internetarchive.html +1 -1
  12. package/CHANGELOG.md +4 -0
  13. package/package.json +4 -2
  14. package/src/BookNavigator/assets/icon_sort_asc.js +5 -0
  15. package/src/BookNavigator/assets/icon_sort_desc.js +5 -0
  16. package/src/BookNavigator/assets/icon_sort_neutral.js +5 -0
  17. package/src/BookNavigator/assets/icon_volumes.js +11 -0
  18. package/src/BookNavigator/book-navigator.js +2 -2
  19. package/src/BookNavigator/sharing.js +5 -5
  20. package/src/BookNavigator/volumes/volumes-provider.js +111 -0
  21. package/src/BookNavigator/volumes/volumes.js +188 -0
  22. package/src/BookReader.js +4 -5
  23. package/src/ia-bookreader/ia-bookreader.js +6 -6
  24. package/src/plugins/tts/AbstractTTSEngine.js +22 -3
  25. package/src/plugins/url/UrlPlugin.js +5 -7
  26. package/tests/e2e/models/Navigation.js +1 -1
  27. package/tests/jest/BookNavigator/book-navigator.test.js +2 -2
  28. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +1 -1
  29. package/tests/jest/BookNavigator/volumes/volumes-provider.test.js +121 -17
  30. package/tests/jest/BookNavigator/volumes/volumes.test.js +97 -0
  31. package/tests/jest/plugins/tts/AbstractTTSEngine.test.js +20 -0
  32. package/tests/jest/plugins/url/UrlPlugin.test.js +8 -0
  33. package/src/BookNavigator/viewable-files.js +0 -95
@@ -1,6 +1,6 @@
1
1
  import { html } from 'lit';
2
- import { iauxShareIcon } from '@internetarchive/ia-item-navigator/dist/src/menus/share-panel';
3
- import '@internetarchive/ia-item-navigator/dist/src/menus/share-panel';
2
+ import '@internetarchive/icon-share/icon-share';
3
+ import '@internetarchive/ia-sharing-options';
4
4
 
5
5
  export default class SharingProvider {
6
6
  constructor({
@@ -12,16 +12,16 @@ export default class SharingProvider {
12
12
  const creatorToUse = Array.isArray(creator) ? creator[0] : creator;
13
13
  const subPrefix = bookreader.options.subPrefix || '';
14
14
  const label = `Share this book`;
15
- this.icon = html`${iauxShareIcon}`;
15
+ this.icon = html`<ia-icon-share style="width: var(--iconWidth); height: var(--iconHeight);"></ia-icon-share>`;
16
16
  this.label = label;
17
17
  this.id = 'share';
18
- this.component = html`<iaux-in-share-panel
18
+ this.component = html`<ia-sharing-options
19
19
  .identifier=${identifier}
20
20
  .type=${`book`}
21
21
  .creator=${creatorToUse}
22
22
  .description=${title}
23
23
  .baseHost=${baseHost}
24
24
  .fileSubPrefix=${subPrefix}
25
- ></iaux-in-share-panel>`;
25
+ ></ia-sharing-options>`;
26
26
  }
27
27
  }
@@ -0,0 +1,111 @@
1
+ import { html } from 'lit';
2
+
3
+ import sortDescIcon from '../assets/icon_sort_desc.js';
4
+ import sortAscIcon from '../assets/icon_sort_asc.js';
5
+ import sortNeutralIcon from '../assets/icon_sort_neutral.js';
6
+ import volumesIcon from '../assets/icon_volumes.js';
7
+
8
+ import './volumes.js';
9
+
10
+ const sortType = {
11
+ title_asc: 'title_asc',
12
+ title_desc: 'title_desc',
13
+ default: 'default'
14
+ };
15
+ export default class VolumesProvider {
16
+ /**
17
+ * @param {import('../../BookReader').default} bookreader
18
+ */
19
+ constructor({ baseHost, bookreader, onProviderChange }) {
20
+ this.onProviderChange = onProviderChange;
21
+ this.component = document.createElement("viewable-files");
22
+
23
+ const files = bookreader.options.multipleBooksList.by_subprefix;
24
+ this.viewableFiles = Object.keys(files).map(item => files[item]);
25
+ this.volumeCount = Object.keys(files).length;
26
+
27
+ /** @type {import('../../BookReader').default} */
28
+ this.bookreader = bookreader;
29
+
30
+ this.component.subPrefix = bookreader.options.subPrefix || "";
31
+ this.component.hostUrl = baseHost;
32
+ this.component.viewableFiles = this.viewableFiles;
33
+
34
+ this.id = "volumes";
35
+ this.label = `Viewable files (${this.volumeCount})`;
36
+ this.icon = html`${volumesIcon}`;
37
+
38
+ this.sortOrderBy = sortType.default;
39
+
40
+ // get sort state from query param
41
+ if (this.bookreader.urlPlugin) {
42
+ this.bookreader.urlPlugin.pullFromAddressBar();
43
+
44
+ const urlSortValue = this.bookreader.urlPlugin.getUrlParam('sort');
45
+ if (urlSortValue === sortType.title_asc || urlSortValue === sortType.title_desc) {
46
+ this.sortOrderBy = urlSortValue;
47
+ }
48
+ }
49
+ this.sortVolumes(this.sortOrderBy);
50
+ }
51
+
52
+ get sortButton() {
53
+ const sortIcons = {
54
+ default: html`
55
+ <button class="sort-by neutral-icon" aria-label="Sort volumes in initial order" @click=${() => this.sortVolumes("title_asc")}>${sortNeutralIcon}</button>
56
+ `,
57
+ title_asc: html`
58
+ <button class="sort-by asc-icon" aria-label="Sort volumes in ascending order" @click=${() => this.sortVolumes("title_desc")}>${sortAscIcon}</button>
59
+ `,
60
+ title_desc: html`
61
+ <button class="sort-by desc-icon" aria-label="Sort volumes in descending order" @click=${() => this.sortVolumes("default")}>${sortDescIcon}</button>
62
+ `,
63
+ };
64
+
65
+ return sortIcons[this.sortOrderBy];
66
+ }
67
+
68
+ /**
69
+ * @param {'default' | 'title_asc' | 'title_desc'} sortByType
70
+ */
71
+ sortVolumes(sortByType) {
72
+ let sortedFiles = [];
73
+
74
+ const files = this.viewableFiles;
75
+ sortedFiles = files.sort((a, b) => {
76
+ if (sortByType === sortType.title_asc) return a.title.localeCompare(b.title);
77
+ else if (sortByType === sortType.title_desc) return b.title.localeCompare(a.title);
78
+ else return a.orig_sort - b.orig_sort;
79
+ });
80
+
81
+ this.sortOrderBy = sortByType;
82
+ this.component.sortOrderBy = sortByType;
83
+ this.component.viewableFiles = [...sortedFiles];
84
+ this.actionButton = this.sortButton;
85
+
86
+ if (this.bookreader.urlPlugin) {
87
+ this.bookreader.urlPlugin.pullFromAddressBar();
88
+ if (this.sortOrderBy !== sortType.default) {
89
+ this.bookreader.urlPlugin.setUrlParam('sort', sortByType);
90
+ } else {
91
+ this.bookreader.urlPlugin.removeUrlParam('sort');
92
+ }
93
+ }
94
+
95
+ this.onProviderChange(this.bookreader);
96
+
97
+ this.multipleFilesClicked(sortByType);
98
+ }
99
+
100
+ /**
101
+ * @param {'default' | 'title_asc' | 'title_desc'} orderBy
102
+ */
103
+ multipleFilesClicked(orderBy) {
104
+ window.archive_analytics?.send_event(
105
+ 'BookReader',
106
+ `VolumesSort|${orderBy}`,
107
+ window.location.path,
108
+ );
109
+ }
110
+
111
+ }
@@ -0,0 +1,188 @@
1
+ import { css, html, LitElement, nothing } from 'lit';
2
+ import { repeat } from 'lit/directives/repeat.js';
3
+
4
+ export class Volumes extends LitElement {
5
+ static get properties() {
6
+ return {
7
+ subPrefix: { type: String },
8
+ hostUrl: { type: String },
9
+ viewableFiles: { type: Array },
10
+ sortOrderBy: { type: String },
11
+ };
12
+ }
13
+
14
+ constructor() {
15
+ super();
16
+ this.hostUrl = '';
17
+ this.sortOrderBy = '';
18
+ this.subPrefix = '';
19
+ this.viewableFiles = [];
20
+ }
21
+
22
+ firstUpdated() {
23
+ const activeFile = this.shadowRoot.querySelector('.content.active');
24
+ // allow for css animations to run before scrolling to active file
25
+ setTimeout(() => {
26
+ // scroll active file into view if needed
27
+ // note: `scrollIntoViewIfNeeded` handles auto-scroll gracefully for Chrome, Safari
28
+ // Firefox does not have this capability yet as it does not support `scrollIntoViewIfNeeded`
29
+ if (activeFile?.scrollIntoViewIfNeeded) {
30
+ activeFile?.scrollIntoViewIfNeeded(true);
31
+ return;
32
+ }
33
+
34
+ // Todo: support `scrollIntoView` or `parentContainer.crollTop = x` for FF & "IE 11"
35
+ // currently, the hard `position: absolutes` misaligns subpanel when `scrollIntoView` is applied :(
36
+ }, 350);
37
+ }
38
+
39
+ volumeItemWithImageTitle(item) {
40
+ const hrefUrl = this.sortOrderBy === 'default'
41
+ ? `${this.hostUrl}${item.url_path}`
42
+ : `${this.hostUrl}${item.url_path}?sort=${this.sortOrderBy}`;
43
+
44
+ return html`
45
+ <li class="content active">
46
+ <div class="separator"></div>
47
+ <a class="container" href="${hrefUrl}">
48
+ <div class="image">
49
+ <img src="${item.image}">
50
+ </div>
51
+ <div class="text">
52
+ <p class="item-title">${item.title}</p>
53
+ <small>by: ${item.author}</small>
54
+ </div>
55
+ </a>
56
+ </li>
57
+ `;
58
+ }
59
+
60
+ volumeItem(item) {
61
+ const activeClass = this.subPrefix === item.file_subprefix ? ' active' : '';
62
+
63
+ const hrefUrl = this.sortOrderBy === 'default'
64
+ ? `${this.hostUrl}${item.url_path}`
65
+ : `${this.hostUrl}${item.url_path}?sort=${this.sortOrderBy}`;
66
+
67
+ return html`
68
+ <li>
69
+ <div class="separator"></div>
70
+ <div class="content${activeClass}">
71
+ <a href="https://${hrefUrl}">
72
+ <p class="item-title">${item.title}</p>
73
+ </a>
74
+ </div>
75
+ </li>
76
+ `;
77
+ }
78
+
79
+ get volumesList() {
80
+ const volumes = repeat(this.viewableFiles, volume => volume?.file_prefix, this.volumeItem.bind(this));
81
+ return html`
82
+ <ul>
83
+ ${volumes}
84
+ <div class="separator"></div>
85
+ </ul>
86
+ `;
87
+ }
88
+
89
+ render() {
90
+ return html`
91
+ ${this.viewableFiles.length ? this.volumesList : nothing}
92
+ `;
93
+ }
94
+
95
+ static get styles() {
96
+ return css`
97
+ :host {
98
+ display: block;
99
+ overflow-y: auto;
100
+ box-sizing: border-box;
101
+ color: var(--primaryTextColor);
102
+ margin-top: 14px;
103
+ margin-bottom: 2rem;
104
+ --activeBorderWidth: 2px;
105
+ }
106
+
107
+ a {
108
+ color: #ffffff;
109
+ text-decoration: none
110
+ }
111
+
112
+ img {
113
+ width: 35px;
114
+ height: 45px;
115
+ }
116
+
117
+ ul {
118
+ padding: 0;
119
+ list-style: none;
120
+ margin: var(--activeBorderWidth) 0.5rem 1rem 0;
121
+ }
122
+
123
+ ul > li:first-child .separator {
124
+ display: none;
125
+ }
126
+
127
+ li {
128
+ cursor: pointer;
129
+ outline: none;
130
+ position: relative;
131
+ }
132
+
133
+ li .content {
134
+ padding: 2px 0 4px 2px;
135
+ border: var(--activeBorderWidth) solid transparent;
136
+ padding: .2rem 0 .4rem .2rem;
137
+ }
138
+
139
+ li .content.active {
140
+ border: var(--activeBorderWidth) solid #538bc5;
141
+ }
142
+
143
+ small {
144
+ font-style: italic;
145
+ white-space: initial;
146
+ }
147
+
148
+ .container {
149
+ display: flex;
150
+ align-items: center;
151
+ justify-content: center
152
+ }
153
+
154
+ .item-title {
155
+ margin-block-start: 0em;
156
+ margin-block-end: 0em;
157
+ font-size: 14px;
158
+ font-weight: bold;
159
+ word-wrap: break-word;
160
+ padding-left: 5px;
161
+ }
162
+
163
+ .separator {
164
+ background-color: var(--secondaryBGColor);
165
+ width: 98%;
166
+ margin: 1px auto;
167
+ height: 1px;
168
+ }
169
+
170
+ .text {
171
+ padding-left: 10px;
172
+ }
173
+
174
+ .icon {
175
+ display: inline-block;
176
+ width: 14px;
177
+ height: 14px;
178
+ margin-left: .7rem;
179
+ border: 1px solid var(--primaryTextColor);
180
+ border-radius: 2px;
181
+ background: var(--activeButtonBg) 50% 50% no-repeat;
182
+ }
183
+
184
+ `;
185
+ }
186
+ }
187
+
188
+ customElements.define('viewable-files', Volumes);
package/src/BookReader.js CHANGED
@@ -1788,7 +1788,7 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
1788
1788
  // Index and page
1789
1789
  if ('undefined' != typeof(urlHash['page'])) {
1790
1790
  // page was set -- may not be int
1791
- params.page = urlHash['page'];
1791
+ params.page = decodeURIComponent(urlHash['page']);
1792
1792
  }
1793
1793
 
1794
1794
  // $$$ process /region
@@ -1820,11 +1820,10 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
1820
1820
  * @return {string}
1821
1821
  */
1822
1822
  BookReader.prototype.fragmentFromParams = function(params, urlMode = 'hash') {
1823
- const separator = '/';
1824
1823
  const fragments = [];
1825
1824
 
1826
1825
  if ('undefined' != typeof(params.page)) {
1827
- fragments.push('page', params.page);
1826
+ fragments.push('page', encodeURIComponent(params.page));
1828
1827
  } else {
1829
1828
  if ('undefined' != typeof(params.index)) {
1830
1829
  // Don't have page numbering but we do have the index
@@ -1850,10 +1849,10 @@ BookReader.prototype.fragmentFromParams = function(params, urlMode = 'hash') {
1850
1849
 
1851
1850
  // search
1852
1851
  if (params.search && urlMode === 'hash') {
1853
- fragments.push('search', params.search);
1852
+ fragments.push('search', utils.encodeURIComponentPlus(params.search));
1854
1853
  }
1855
1854
 
1856
- return utils.encodeURIComponentPlus(fragments.join(separator)).replace(/%2F/g, '/');
1855
+ return fragments.join('/');
1857
1856
  };
1858
1857
 
1859
1858
  /**
@@ -30,7 +30,7 @@ export class IaBookReader extends LitElement {
30
30
  super();
31
31
  this.item = undefined;
32
32
  this.bookreader = undefined;
33
- this.baseHost = 'archive.org';
33
+ this.baseHost = 'https://archive.org';
34
34
  this.fullscreen = false;
35
35
  this.signedIn = false;
36
36
  /** @type {ModalManager} */
@@ -54,7 +54,7 @@ export class IaBookReader extends LitElement {
54
54
  }
55
55
 
56
56
  get itemNav() {
57
- return this.shadowRoot.querySelector('iaux-item-navigator');
57
+ return this.shadowRoot.querySelector('ia-item-navigator');
58
58
  }
59
59
 
60
60
  /** Creates modal DOM & attaches to `<body>` */
@@ -111,7 +111,7 @@ export class IaBookReader extends LitElement {
111
111
  render() {
112
112
  return html`
113
113
  <div class="main-component">
114
- <iaux-item-navigator
114
+ <ia-item-navigator
115
115
  ?viewportInFullscreen=${this.fullscreen}
116
116
  .basehost=${this.baseHost}
117
117
  .item=${this.item}
@@ -145,7 +145,7 @@ export class IaBookReader extends LitElement {
145
145
  </div>
146
146
  </book-navigator>
147
147
  </div>
148
- </iaux-item-navigator>
148
+ </ia-item-navigator>
149
149
  </div>
150
150
  `;
151
151
  }
@@ -169,7 +169,7 @@ export class IaBookReader extends LitElement {
169
169
  }
170
170
 
171
171
  :host([fullscreen]),
172
- iaux-item-navigator[viewportinfullscreen] {
172
+ ia-item-navigator[viewportinfullscreen] {
173
173
  position: fixed;
174
174
  inset: 0;
175
175
  height: 100%;
@@ -193,7 +193,7 @@ export class IaBookReader extends LitElement {
193
193
  flex: 1;
194
194
  }
195
195
 
196
- iaux-item-navigator {
196
+ ia-item-navigator {
197
197
  min-height: var(--br-height, inherit);
198
198
  height: var(--br-height, inherit);
199
199
  display: block;
@@ -142,6 +142,10 @@ export default class AbstractTTSEngine {
142
142
  // MS Edge fires voices changed randomly very often
143
143
  this.events.off('voiceschanged', this.updateBestVoice);
144
144
  this.voice = this.getVoices().find(voice => voice.voiceURI === voiceURI);
145
+ // if the current book has a language set, store the selected voice with the book language as a suffix
146
+ if (this.opts.bookLanguage) {
147
+ localStorage.setItem(`BRtts-voice-${this.opts.bookLanguage}`, this.voice.voiceURI);
148
+ }
145
149
  if (this.activeSound) this.activeSound.setVoice(this.voice);
146
150
  }
147
151
 
@@ -221,10 +225,12 @@ export default class AbstractTTSEngine {
221
225
  // user languages that match the book language
222
226
  const matchingUserLangs = userLanguages.filter(lang => lang.startsWith(bookLanguage));
223
227
 
224
- // Try to find voices that intersect these two sets
225
- return AbstractTTSEngine.getMatchingVoice(matchingUserLangs, bookLangVoices) ||
228
+ // First try to find the last chosen voice from localStorage for the current book language
229
+ return AbstractTTSEngine.getMatchingStoredVoice(bookLangVoices, bookLanguage)
230
+ // Try to find voices that intersect these two sets
231
+ || AbstractTTSEngine.getMatchingVoice(matchingUserLangs, bookLangVoices)
226
232
  // no user languages match the books; let's return the best voice for the book language
227
- (bookLangVoices.find(v => v.default) || bookLangVoices[0])
233
+ || (bookLangVoices.find(v => v.default) || bookLangVoices[0])
228
234
  // No voices match the book language? let's find a voice in the user's language
229
235
  // and ignore book lang
230
236
  || AbstractTTSEngine.getMatchingVoice(userLanguages, voices)
@@ -232,6 +238,19 @@ export default class AbstractTTSEngine {
232
238
  || (voices.find(v => v.default) || voices[0]);
233
239
  }
234
240
 
241
+ /**
242
+ * @private
243
+ * Get the voice last selected by the user for the book language from localStorage.
244
+ * Returns undefined if no voice is stored or found.
245
+ * @param {SpeechSynthesisVoice[]} voices browser voices to choose from
246
+ * @param {ISO6391} bookLanguage book language to look for
247
+ * @return {SpeechSynthesisVoice | undefined}
248
+ */
249
+ static getMatchingStoredVoice(voices, bookLanguage) {
250
+ const storedVoice = localStorage.getItem(`BRtts-voice-${bookLanguage}`);
251
+ return (storedVoice ? voices.find(v => v.voiceURI === storedVoice) : undefined);
252
+ }
253
+
235
254
  /**
236
255
  * @private
237
256
  * Get the best voice that matches one of the BCP47 languages (order by preference)
@@ -45,7 +45,7 @@ export class UrlPlugin {
45
45
 
46
46
  const strPathParams = this.urlSchema
47
47
  .filter(s => s.position == 'path')
48
- .map(schema => pathParams[schema.name] ? `${schema.name}/${pathParams[schema.name]}` : '')
48
+ .map(schema => pathParams[schema.name] ? `${schema.name}/${encodeURIComponent(pathParams[schema.name])}` : '')
49
49
  .join('/');
50
50
 
51
51
  // replace consecutive slashes with a single slash + remove trailing slashes
@@ -83,15 +83,13 @@ export class UrlPlugin {
83
83
  const hasPropertyKey = doesKeyExists(urlStrSplitSlashObj, schema.name);
84
84
  const hasDeprecatedKey = doesKeyExists(schema, 'deprecated_for') && hasPropertyKey;
85
85
 
86
- if (hasDeprecatedKey) {
87
- urlState[schema.deprecated_for] = urlStrSplitSlashObj[schema.name];
86
+ // Not in the URL
87
+ if (!hasPropertyKey && !hasDeprecatedKey) {
88
88
  return;
89
89
  }
90
90
 
91
- if (hasPropertyKey) {
92
- urlState[schema.name] = urlStrSplitSlashObj[schema.name];
93
- return;
94
- }
91
+ const urlStateParam = hasDeprecatedKey ? schema.deprecated_for : schema.name;
92
+ urlState[urlStateParam] = decodeURIComponent(urlStrSplitSlashObj[schema.name]);
95
93
  });
96
94
 
97
95
  // Add searchParams to urlState
@@ -5,7 +5,7 @@ export default class Navigation {
5
5
  constructor() {
6
6
  this.topNavShell = new Selector('.BRtoolbar');
7
7
  this.bottomNavShell = new Selector('.BRfooter');
8
- this.itemNav = Selector('ia-bookreader').shadowRoot().find('iaux-item-navigator').shadowRoot();
8
+ this.itemNav = Selector('ia-bookreader').shadowRoot().find('ia-item-navigator').shadowRoot();
9
9
 
10
10
  // flipping
11
11
  this.goLeft = this.bottomNavShell.find('.BRicon.book_left');
@@ -9,7 +9,7 @@ import DownloadsProvider from '@/src/BookNavigator/downloads/downloads-provider.
9
9
  import SearchProvider from '@/src/BookNavigator/search/search-provider.js';
10
10
  import SharingProvider from '@/src/BookNavigator/sharing.js';
11
11
  import VisualAdjustmentsProvider from '@/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js';
12
- import ViewableFilesProvider from '@/src/BookNavigator/viewable-files.js';
12
+ import VolumesProvider from '@/src/BookNavigator/volumes/volumes-provider.js';
13
13
  import { ModalManager } from '@internetarchive/modal-manager';
14
14
  import { SharedResizeObserver } from '@internetarchive/shared-resize-observer';
15
15
  import '@/src/BookNavigator/book-navigator.js';
@@ -214,7 +214,7 @@ describe('<book-navigator>', () => {
214
214
  await el.elementUpdated;
215
215
 
216
216
  expect(el.menuProviders.volumes).toBeDefined();
217
- expect(el.menuProviders.volumes).toBeInstanceOf(ViewableFilesProvider);
217
+ expect(el.menuProviders.volumes).toBeInstanceOf(VolumesProvider);
218
218
 
219
219
  // also adds a menu shortcut
220
220
  expect(el.menuShortcuts.find(m => m.id === 'volumes')).toBeDefined();
@@ -30,7 +30,7 @@ describe('Sharing Provider', () => {
30
30
  expect(provider.id).toEqual('share');
31
31
  expect(provider.icon).toBeDefined();
32
32
  expect(provider.label).toEqual('Share this book');
33
- expect(fixtureSync(provider.component).tagName).toContain('IAUX-IN-SHARE-PANEL');
33
+ expect(fixtureSync(provider.component).tagName).toContain('IA-SHARING-OPTIONS');
34
34
  });
35
35
 
36
36
  describe('Handles being a sub file/volume', () => {