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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. package/BookReader/BookReader.js +1 -1
  2. package/BookReader/BookReader.js.map +1 -1
  3. package/BookReader/ia-bookreader-bundle.js +572 -293
  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/CHANGELOG.md +0 -4
  12. package/package.json +2 -4
  13. package/src/BookNavigator/sharing.js +5 -5
  14. package/src/BookNavigator/volumes/volumes-provider.js +37 -53
  15. package/src/BookReader.js +5 -4
  16. package/src/ia-bookreader/ia-bookreader.js +5 -5
  17. package/src/plugins/tts/AbstractTTSEngine.js +3 -22
  18. package/src/plugins/url/UrlPlugin.js +7 -5
  19. package/tests/e2e/models/Navigation.js +1 -1
  20. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +1 -1
  21. package/tests/jest/BookNavigator/volumes/volumes-provider.test.js +15 -119
  22. package/tests/jest/plugins/tts/AbstractTTSEngine.test.js +0 -20
  23. package/tests/jest/plugins/url/UrlPlugin.test.js +0 -8
  24. package/src/BookNavigator/assets/icon_sort_asc.js +0 -5
  25. package/src/BookNavigator/assets/icon_sort_desc.js +0 -5
  26. package/src/BookNavigator/assets/icon_sort_neutral.js +0 -5
  27. package/src/BookNavigator/assets/icon_volumes.js +0 -11
  28. package/src/BookNavigator/volumes/volumes.js +0 -188
  29. package/tests/jest/BookNavigator/volumes/volumes.test.js +0 -97
@@ -1,13 +1,12 @@
1
1
  import { html } from 'lit';
2
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';
3
+ import { viewableFilesIcon } from '@internetarchive/ia-item-navigator';
4
+ import '@internetarchive/ia-item-navigator';
7
5
 
8
- import './volumes.js';
9
-
10
- const sortType = {
6
+ /**
7
+ * * @typedef { 'title_asc' | 'title_desc' | 'default'} SortTypesT
8
+ */
9
+ const sortTypes = {
11
10
  title_asc: 'title_asc',
12
11
  title_desc: 'title_desc',
13
12
  default: 'default'
@@ -17,76 +16,61 @@ export default class VolumesProvider {
17
16
  * @param {import('../../BookReader').default} bookreader
18
17
  */
19
18
  constructor({ baseHost, bookreader, onProviderChange }) {
19
+ /** @type {import('../../BookReader').default} */
20
+ this.bookreader = bookreader;
20
21
  this.onProviderChange = onProviderChange;
21
- this.component = document.createElement("viewable-files");
22
+ this.baseHost = baseHost;
22
23
 
23
24
  const files = bookreader.options.multipleBooksList.by_subprefix;
24
25
  this.viewableFiles = Object.keys(files).map(item => files[item]);
25
26
  this.volumeCount = Object.keys(files).length;
26
27
 
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
28
  this.id = "volumes";
35
29
  this.label = `Viewable files (${this.volumeCount})`;
36
- this.icon = html`${volumesIcon}`;
30
+ this.icon = html`${viewableFilesIcon}`;
31
+ this.sortOrderBy = sortTypes.default;
37
32
 
38
- this.sortOrderBy = sortType.default;
33
+ this.component = document.createElement("iaux-viewable-files");
34
+ this.component.addSortToUrl = true;
35
+ this.component.subPrefix = bookreader.options.subPrefix || "";
36
+ this.component.baseHost = baseHost;
37
+ this.component.fileList = [...this.viewableFiles];
38
+
39
+ this.sortFilesComponent = document.createElement("iaux-sort-viewable-files");
40
+ this.sortFilesComponent.fileListRaw = this.viewableFiles;
41
+ this.sortFilesComponent.addEventListener('fileListSorted', (e) => this.handleFileListSorted(e));
42
+ this.actionButton = this.sortFilesComponent;
39
43
 
40
44
  // get sort state from query param
41
45
  if (this.bookreader.urlPlugin) {
42
46
  this.bookreader.urlPlugin.pullFromAddressBar();
43
47
 
44
48
  const urlSortValue = this.bookreader.urlPlugin.getUrlParam('sort');
45
- if (urlSortValue === sortType.title_asc || urlSortValue === sortType.title_desc) {
49
+ if (urlSortValue === sortTypes.title_asc || urlSortValue === sortTypes.title_desc) {
46
50
  this.sortOrderBy = urlSortValue;
47
51
  }
48
52
  }
49
- this.sortVolumes(this.sortOrderBy);
50
- }
51
53
 
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];
54
+ this.sortFilesComponent.sortVolumes(this.sortOrderBy);
55
+
56
+ this.onProviderChange(this.bookreader);
66
57
  }
67
58
 
68
- /**
69
- * @param {'default' | 'title_asc' | 'title_desc'} sortByType
70
- */
71
- sortVolumes(sortByType) {
72
- let sortedFiles = [];
59
+ /** @param { SortTypesT } sortType */
60
+ async handleFileListSorted(event) {
61
+ const { sortType, sortedFiles } = event.detail;
73
62
 
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
- });
63
+ this.viewableFiles = sortedFiles;
64
+ this.sortOrderBy = sortType;
80
65
 
81
- this.sortOrderBy = sortByType;
82
- this.component.sortOrderBy = sortByType;
83
- this.component.viewableFiles = [...sortedFiles];
84
- this.actionButton = this.sortButton;
66
+ // update the component
67
+ this.component.fileList = [...this.viewableFiles];
68
+ await this.component.updateComplete;
85
69
 
86
70
  if (this.bookreader.urlPlugin) {
87
71
  this.bookreader.urlPlugin.pullFromAddressBar();
88
- if (this.sortOrderBy !== sortType.default) {
89
- this.bookreader.urlPlugin.setUrlParam('sort', sortByType);
72
+ if (this.sortOrderBy !== sortTypes.default) {
73
+ this.bookreader.urlPlugin.setUrlParam('sort', this.sortOrderBy);
90
74
  } else {
91
75
  this.bookreader.urlPlugin.removeUrlParam('sort');
92
76
  }
@@ -94,11 +78,11 @@ export default class VolumesProvider {
94
78
 
95
79
  this.onProviderChange(this.bookreader);
96
80
 
97
- this.multipleFilesClicked(sortByType);
81
+ this.multipleFilesClicked(this.sortOrderBy);
98
82
  }
99
83
 
100
84
  /**
101
- * @param {'default' | 'title_asc' | 'title_desc'} orderBy
85
+ * @param { SortTypesT } orderBy
102
86
  */
103
87
  multipleFilesClicked(orderBy) {
104
88
  window.archive_analytics?.send_event(
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 = decodeURIComponent(urlHash['page']);
1791
+ params.page = urlHash['page'];
1792
1792
  }
1793
1793
 
1794
1794
  // $$$ process /region
@@ -1820,10 +1820,11 @@ BookReader.prototype.paramsFromFragment = function(fragment) {
1820
1820
  * @return {string}
1821
1821
  */
1822
1822
  BookReader.prototype.fragmentFromParams = function(params, urlMode = 'hash') {
1823
+ const separator = '/';
1823
1824
  const fragments = [];
1824
1825
 
1825
1826
  if ('undefined' != typeof(params.page)) {
1826
- fragments.push('page', encodeURIComponent(params.page));
1827
+ fragments.push('page', params.page);
1827
1828
  } else {
1828
1829
  if ('undefined' != typeof(params.index)) {
1829
1830
  // Don't have page numbering but we do have the index
@@ -1849,10 +1850,10 @@ BookReader.prototype.fragmentFromParams = function(params, urlMode = 'hash') {
1849
1850
 
1850
1851
  // search
1851
1852
  if (params.search && urlMode === 'hash') {
1852
- fragments.push('search', utils.encodeURIComponentPlus(params.search));
1853
+ fragments.push('search', params.search);
1853
1854
  }
1854
1855
 
1855
- return fragments.join('/');
1856
+ return utils.encodeURIComponentPlus(fragments.join(separator)).replace(/%2F/g, '/');
1856
1857
  };
1857
1858
 
1858
1859
  /**
@@ -54,7 +54,7 @@ export class IaBookReader extends LitElement {
54
54
  }
55
55
 
56
56
  get itemNav() {
57
- return this.shadowRoot.querySelector('ia-item-navigator');
57
+ return this.shadowRoot.querySelector('iaux-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
- <ia-item-navigator
114
+ <iaux-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
- </ia-item-navigator>
148
+ </iaux-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
- ia-item-navigator[viewportinfullscreen] {
172
+ iaux-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
- ia-item-navigator {
196
+ iaux-item-navigator {
197
197
  min-height: var(--br-height, inherit);
198
198
  height: var(--br-height, inherit);
199
199
  display: block;
@@ -142,10 +142,6 @@ 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
- }
149
145
  if (this.activeSound) this.activeSound.setVoice(this.voice);
150
146
  }
151
147
 
@@ -225,12 +221,10 @@ export default class AbstractTTSEngine {
225
221
  // user languages that match the book language
226
222
  const matchingUserLangs = userLanguages.filter(lang => lang.startsWith(bookLanguage));
227
223
 
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)
224
+ // Try to find voices that intersect these two sets
225
+ return AbstractTTSEngine.getMatchingVoice(matchingUserLangs, bookLangVoices) ||
232
226
  // no user languages match the books; let's return the best voice for the book language
233
- || (bookLangVoices.find(v => v.default) || bookLangVoices[0])
227
+ (bookLangVoices.find(v => v.default) || bookLangVoices[0])
234
228
  // No voices match the book language? let's find a voice in the user's language
235
229
  // and ignore book lang
236
230
  || AbstractTTSEngine.getMatchingVoice(userLanguages, voices)
@@ -238,19 +232,6 @@ export default class AbstractTTSEngine {
238
232
  || (voices.find(v => v.default) || voices[0]);
239
233
  }
240
234
 
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
-
254
235
  /**
255
236
  * @private
256
237
  * 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}/${encodeURIComponent(pathParams[schema.name])}` : '')
48
+ .map(schema => pathParams[schema.name] ? `${schema.name}/${pathParams[schema.name]}` : '')
49
49
  .join('/');
50
50
 
51
51
  // replace consecutive slashes with a single slash + remove trailing slashes
@@ -83,13 +83,15 @@ export class UrlPlugin {
83
83
  const hasPropertyKey = doesKeyExists(urlStrSplitSlashObj, schema.name);
84
84
  const hasDeprecatedKey = doesKeyExists(schema, 'deprecated_for') && hasPropertyKey;
85
85
 
86
- // Not in the URL
87
- if (!hasPropertyKey && !hasDeprecatedKey) {
86
+ if (hasDeprecatedKey) {
87
+ urlState[schema.deprecated_for] = urlStrSplitSlashObj[schema.name];
88
88
  return;
89
89
  }
90
90
 
91
- const urlStateParam = hasDeprecatedKey ? schema.deprecated_for : schema.name;
92
- urlState[urlStateParam] = decodeURIComponent(urlStrSplitSlashObj[schema.name]);
91
+ if (hasPropertyKey) {
92
+ urlState[schema.name] = urlStrSplitSlashObj[schema.name];
93
+ return;
94
+ }
93
95
  });
94
96
 
95
97
  // 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('ia-item-navigator').shadowRoot();
8
+ this.itemNav = Selector('ia-bookreader').shadowRoot().find('iaux-item-navigator').shadowRoot();
9
9
 
10
10
  // flipping
11
11
  this.goLeft = this.bottomNavShell.find('.BRicon.book_left');
@@ -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('IA-SHARING-OPTIONS');
33
+ expect(fixtureSync(provider.component).tagName).toContain('IAUX-SHARING-OPTIONS');
34
34
  });
35
35
 
36
36
  describe('Handles being a sub file/volume', () => {
@@ -1,9 +1,10 @@
1
- import { fixture, fixtureCleanup, fixtureSync } from '@open-wc/testing-helpers';
1
+ import { fixtureCleanup, fixtureSync } from '@open-wc/testing-helpers';
2
2
  import sinon from 'sinon';
3
3
  import volumesProvider from '@/src/BookNavigator/volumes/volumes-provider';
4
4
 
5
5
  const brOptions = {
6
6
  "options": {
7
+ "subPrefix": 'special-subprefix',
7
8
  "enableMultipleBooks": true,
8
9
  "multipleBooksList": {
9
10
  "by_subprefix": {
@@ -39,8 +40,9 @@ afterEach(() => {
39
40
  });
40
41
 
41
42
  describe('Volumes Provider', () => {
42
- test('constructor', () => {
43
+ test('initiating & sorting', () => {
43
44
  const onProviderChange = sinon.fake();
45
+
44
46
  const baseHost = "https://archive.org";
45
47
  const provider = new volumesProvider({
46
48
  baseHost,
@@ -55,130 +57,24 @@ describe('Volumes Provider', () => {
55
57
  expect(provider.id).toEqual('volumes');
56
58
  expect(provider.icon).toBeDefined();
57
59
  expect(fixtureSync(provider.icon).tagName).toEqual('svg');
60
+ expect(provider.sortOrderBy).toEqual('default');
61
+
58
62
  expect(provider.label).toEqual(`Viewable files (${volumeCount})`);
59
63
  expect(provider.viewableFiles).toBeDefined();
60
64
  expect(provider.viewableFiles.length).toEqual(3);
65
+ expect(provider.volumeCount).toEqual(3);
61
66
 
62
- expect(provider.component.hostUrl).toBeDefined();
63
- expect(provider.component.hostUrl).toEqual(baseHost);
64
- expect(provider.component).toBeDefined();
65
- });
66
-
67
- test('sorting cycles - render sort actionButton', async () => {
68
- const onProviderChange = sinon.fake();
69
- const baseHost = "https://archive.org";
70
- const provider = new volumesProvider({
71
- baseHost,
72
- bookreader: brOptions,
73
- onProviderChange
74
- });
75
-
76
- expect(provider.sortOrderBy).toEqual("default");
77
-
78
- provider.sortVolumes("title_asc");
79
- expect(provider.sortOrderBy).toEqual("title_asc");
80
- expect(fixtureSync(provider.sortButton).outerHTML).toContain("sort-by asc-icon");
81
-
82
- provider.sortVolumes("title_desc");
83
- expect(provider.sortOrderBy).toEqual("title_desc");
84
- expect(fixtureSync(provider.sortButton).outerHTML).toContain("sort-by desc-icon");
85
-
86
- provider.sortVolumes("default");
87
- expect(provider.sortOrderBy).toEqual("default");
88
- expect(fixtureSync(provider.sortButton).outerHTML).toContain("sort-by neutral-icon");
89
- });
67
+ expect(provider.component.baseHost).toEqual(baseHost);
68
+ expect(provider.component.fileList).toEqual(provider.viewableFiles);
69
+ expect(provider.component.subPrefix).toEqual(brOptions.options.subPrefix);
90
70
 
91
- test('sort volumes in initial order', async () => {
92
- const onProviderChange = sinon.fake();
93
- const baseHost = "https://archive.org";
94
- const provider = new volumesProvider({
95
- baseHost,
96
- bookreader: brOptions,
97
- onProviderChange
98
- });
99
-
100
- const parsedFiles = brOptions.options.multipleBooksList.by_subprefix;
101
- const files = Object.keys(parsedFiles).map(item => parsedFiles[item]).sort((a, b) => a.orig_sort - b.orig_sort);
102
- const origSortTitles = files.map(item => item.title);
103
-
104
- provider.sortVolumes("default");
105
-
106
- expect(provider.sortOrderBy).toEqual("default");
107
71
  expect(provider.actionButton).toBeDefined();
72
+ expect(provider.actionButton).toEqual(provider.sortFilesComponent);
73
+ expect(provider.actionButton.fileListRaw).toEqual(provider.viewableFiles);
108
74
 
109
- const providerFileTitles = provider.viewableFiles.map(item => item.title);
110
- // use `.eql` for "lose equality" in order to deeply compare values.
111
- expect(providerFileTitles).toEqual([...origSortTitles]);
112
- });
75
+ const callbackSpy = sinon.spy(provider, 'handleFileListSorted');
76
+ provider.actionButton.sortVolumes('title_asc');
113
77
 
114
- test('sort volumes in ascending title order', async () => {
115
- const onProviderChange = sinon.fake();
116
- const baseHost = "https://archive.org";
117
- const provider = new volumesProvider({
118
- baseHost,
119
- bookreader: brOptions,
120
- onProviderChange
121
- });
122
-
123
- const parsedFiles = brOptions.options.multipleBooksList.by_subprefix;
124
- const files = Object.keys(parsedFiles).map(item => parsedFiles[item]);
125
- const ascendingTitles = files.map(item => item.title).sort((a, b) => a.localeCompare(b));
126
-
127
- provider.sortVolumes("title_asc");
128
-
129
- expect(provider.sortOrderBy).toEqual("title_asc");
130
- expect(provider.actionButton).toBeDefined();
131
-
132
- const providerFileTitles = provider.viewableFiles.map(item => item.title);
133
- // use `.eql` for "lose equality" in order to deeply compare values.
134
- expect(providerFileTitles).toEqual([...ascendingTitles]);
135
- });
136
-
137
- test('sort volumes in descending title order', async () => {
138
- const onProviderChange = sinon.fake();
139
- const baseHost = "https://archive.org";
140
- const provider = new volumesProvider({
141
- baseHost,
142
- bookreader: brOptions,
143
- onProviderChange
144
- });
145
- provider.isSortAscending = false;
146
-
147
- const parsedFiles = brOptions.options.multipleBooksList.by_subprefix;
148
- const files = Object.keys(parsedFiles).map(item => parsedFiles[item]);
149
- const descendingTitles = files.map(item => item.title).sort((a, b) => b.localeCompare(a));
150
-
151
- provider.sortVolumes("title_desc");
152
-
153
- expect(provider.sortOrderBy).toEqual("title_desc");
154
- expect(provider.actionButton).toBeDefined();
155
-
156
- const providerFileTitles = provider.viewableFiles.map(item => item.title);
157
- // use `.eql` for "lose equality" in order to deeply compare values.
158
- expect(providerFileTitles).toEqual([...descendingTitles]);
159
- });
160
-
161
- describe('Sorting icons', () => {
162
- test('has 3 icons', async () => {
163
- const onProviderChange = sinon.fake();
164
- const baseHost = "https://archive.org";
165
- const provider = new volumesProvider({
166
- baseHost,
167
- bookreader: brOptions,
168
- onProviderChange
169
- });
170
- provider.sortOrderBy = 'default';
171
-
172
- const origSortButton = await fixture(provider.sortButton);
173
- expect(origSortButton.classList.contains('neutral-icon')).toBeTruthy();
174
-
175
- provider.sortOrderBy = 'title_asc';
176
- const ascButton = await fixture(provider.sortButton);
177
- expect(ascButton.classList.contains('asc-icon')).toBeTruthy();
178
-
179
- provider.sortOrderBy = 'title_desc';
180
- const descButton = await fixture(provider.sortButton);
181
- expect(descButton.classList.contains('desc-icon')).toBeTruthy();
182
- });
78
+ expect(callbackSpy.callCount).toEqual(1);
183
79
  });
184
80
  });
@@ -111,26 +111,6 @@ for (const dummyVoice of [dummyVoiceHyphens, dummyVoiceUnderscores]) {
111
111
 
112
112
  expect(getBestBookVoice(voices, 'en', ['en-CA', 'en'])).toBe(voices[0]);
113
113
  });
114
-
115
- test('choose stored language from localStorage', () => {
116
- const voices = [
117
- dummyVoice({lang: "en-US", voiceURI: "English US", default: true}),
118
- dummyVoice({lang: "en-GB", voiceURI: "English GB"}),
119
- dummyVoice({lang: "en-CA", voiceURI: "English CA"}),
120
- ];
121
- class DummyEngine extends AbstractTTSEngine {
122
- getVoices() { return voices; }
123
- }
124
- const ttsEngine = new DummyEngine({...DUMMY_TTS_ENGINE_OPTS, bookLanguage: 'en'});
125
- // simulates setting default voice on tts startup
126
- ttsEngine.updateBestVoice();
127
- // simulates user choosing a voice that matches the bookLanguage
128
- // voice will be stored in localStorage
129
- ttsEngine.setVoice(voices[2].voiceURI);
130
-
131
- // expecting the voice to be selected by getMatchingStoredVoice and returned as best voice
132
- expect(getBestBookVoice(voices, 'en', [])).toBe(voices[2]);
133
- });
134
114
  });
135
115
  }
136
116
 
@@ -19,10 +19,6 @@ describe('UrlPlugin tests', () => {
19
19
  expect(urlPlugin.urlStateToUrlString(urlStateWithQueries)).toBe(expectedUrlFromStateWithQueries);
20
20
  });
21
21
 
22
- test('encodes page number', () => {
23
- expect(urlPlugin.urlStateToUrlString({ page: '12/46' })).toBe(`page/12%2F46`);
24
- });
25
-
26
22
  test('urlStateToUrlString with unknown states in schema', () => {
27
23
  const urlState = { page: 'n7', mode: '1up' };
28
24
  const urlStateWithQueries = { page: 'n7', mode: '1up', q: 'hello', viewer: 'theater', sortBy: 'title_asc' };
@@ -51,10 +47,6 @@ describe('UrlPlugin tests', () => {
51
47
  expect(urlPlugin.urlStringToUrlState(url1)).toEqual({page: 'n7', mode: '1up'});
52
48
  });
53
49
 
54
- test('decodes page number', () => {
55
- expect(urlPlugin.urlStringToUrlState('/page/12%2F46')).toStrictEqual({ page: '12/46' });
56
- });
57
-
58
50
  test('urlStringToUrlState with deprecated_for', () => {
59
51
  const url = '/page/n7/mode/2up/search/hello';
60
52
 
@@ -1,5 +0,0 @@
1
- import { html } from 'lit';
2
-
3
- export default html`
4
- <svg name="sort-asc" height="18" viewBox="0 0 18 18" width="18" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="m2.32514544 8.30769231.7756949-2.08468003h2.92824822l.75630252 2.08468003h1.01809955l-2.70523594-6.92307693h-1.01809955l-2.69553976 6.92307693zm3.41305753-2.86037492h-2.34647705l1.17323853-3.22883h.01939237z" fill="#fff" fill-rule="nonzero"/><path d="m7.1689722 16.6153846v-.7756949h-4.4117647l4.29541047-5.3716871v-.77569491h-5.06140918v.77569491h3.97543633l-4.30510666 5.3716871v.7756949z" fill="#fff" fill-rule="nonzero"/><path d="m10.3846154 11.0769231 2.7692308 5.5384615 2.7692307-5.5384615m-2.7692307 4.1538461v-13.15384612" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.661538" transform="matrix(1 0 0 -1 0 18.692308)"/></g></svg>
5
- `;
@@ -1,5 +0,0 @@
1
- import { html } from 'lit';
2
-
3
- export default html`
4
- <svg name="sort-desc" height="18" viewBox="0 0 18 18" width="18" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="m2.32514544 8.30769231.7756949-2.08468003h2.92824822l.75630252 2.08468003h1.01809955l-2.70523594-6.92307693h-1.01809955l-2.69553976 6.92307693zm3.41305753-2.86037492h-2.34647705l1.17323853-3.22883h.01939237z" fill="#fff" fill-rule="nonzero"/><path d="m7.1689722 16.6153846v-.7756949h-4.4117647l4.29541047-5.3716871v-.77569491h-5.06140918v.77569491h3.97543633l-4.30510666 5.3716871v.7756949z" fill="#fff" fill-rule="nonzero"/><path d="m10.3846154 11.0769231 2.7692308 5.5384615 2.7692307-5.5384615m-2.7692307 4.1538461v-13.15384612" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.661538"/></g></svg>
5
- `;
@@ -1,5 +0,0 @@
1
- import { html } from 'lit';
2
-
3
- export default html`
4
- <svg name="sort-neutral" height="18" viewBox="0 0 18 18" width="18" xmlns="http://www.w3.org/2000/svg"><g fill="#fff" fill-rule="evenodd"><path d="m2.32514544 8.30769231.7756949-2.08468003h2.92824822l.75630252 2.08468003h1.01809955l-2.70523594-6.92307693h-1.01809955l-2.69553976 6.92307693zm3.41305753-2.86037492h-2.34647705l1.17323853-3.22883h.01939237z" fill-rule="nonzero"/><path d="m7.1689722 16.6153846v-.7756949h-4.4117647l4.29541047-5.3716871v-.77569491h-5.06140918v.77569491h3.97543633l-4.30510666 5.3716871v.7756949z" fill-rule="nonzero"/><circle cx="13" cy="9" r="2"/></g></svg>
5
- `;
@@ -1,11 +0,0 @@
1
- import { html } from 'lit';
2
-
3
- export default html`
4
- <svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" aria-labelledby="volumesTitleID volumesDescID">
5
- <title id="volumesTitleID">Volumes icon</title>
6
- <desc id="volumesDescID">Three books stacked on each other</desc>
7
- <g fill="#ffffff">
8
- <path fill="#ffffff" d="m9.83536396 0h10.07241114c.1725502.47117517.3378411.76385809.4958725.87804878.1295523.11419069.3199719.1998337.5712586.25692905.2512868.05709534.4704647.08564301.6575337.08564301h.2806036v15.24362526h-4.3355343v3.8106985h-4.44275v3.7250554h-12.01318261c-.27306495 0-.50313194-.085643-.69020098-.256929-.18706903-.1712861-.30936193-.3425721-.36687867-.5138581l-.06449694-.2785477v-14.2159091c0-.32815965.08627512-.5922949.25882537-.79240577.17255024-.20011086.34510049-.32150776.51765073-.36419068l.25882537-.0640244h3.36472977v-2.54767184c0-.31374722.08627513-.57067627.25882537-.77078714.17255025-.20011086.34510049-.32150776.51765074-.36419068l.25882536-.06402439h3.36472978v-2.56929047c0-.32815964.08627512-.5922949.25882537-.79240576.17255024-.20011087.34510049-.31430156.51765073-.34257207zm10.78355264 15.6294346v-13.53076498c-.2730649-.08536585-.4456152-.16380266-.5176507-.23531042-.1725502-.1424612-.2730649-.27078714-.3015441-.38497783v13.36031043h-9.87808272c0 .0144124-.02149898.0144124-.06449694 0-.04299795-.0144124-.08962561.006929-.13988296.0640244-.05025735.0570953-.07538603.1427383-.07538603.256929s.02149898.210643.06449694.289357c.04299795.078714.08599591.1322062.12899387.1604767l.06449693.0216187h10.71905571zm-10.2449613-2.4412417h7.98003v-11.60421286h-7.98003zm1.6827837-9.41990022h4.6153002c.1725502 0 .3199718.05349224.4422647.16047672s.1834393.23891353.1834393.39578714c0 .15687362-.0611464.28519956-.1834393.38497783s-.2697145.1496674-.4422647.1496674h-4.6153002c-.1725503 0-.3199719-.04988913-.4422647-.1496674-.1222929-.09977827-.1834394-.22810421-.1834394-.38497783 0-.15687361.0611465-.28880266.1834394-.39578714.1222928-.10698448.2697144-.16047672.4422647-.16047672zm-6.08197737 13.50997782h7.72120467v-.8131929h-3.79610541c-.27306495 0-.49950224-.085643-.67931188-.256929-.17980964-.1712861-.29847284-.3425721-.35598958-.5138581l-.06449694-.2785477v-10.02023282h-2.82530086zm6.77217827-11.36890243h3.2139578c.1295522 0 .240956.05709534.3342113.17128603.0932554.11419069.139883.24972284.139883.40659645 0 .15687362-.0466276.28880267-.139883.39578714-.0932553.10698448-.2046591.16047672-.3342113.16047672h-3.2139578c-.1295523 0-.2373264-.05349224-.3233223-.16047672-.0859959-.10698447-.1289938-.23891352-.1289938-.39578714 0-.15687361.0429979-.29240576.1289938-.40659645s.19377-.17128603.3233223-.17128603zm-11.15043132 15.11557653h7.69942646v-.7491685h-3.79610539c-.25854616 0-.48135376-.0892462-.66842279-.2677384-.18706904-.1784922-.30936193-.3605876-.36687868-.546286l-.06449694-.2569291v-10.04101994h-2.80352266zm14.62237682-4.5606985h-.8191949v2.1410754h-9.89986085s-.04299796.0285477-.12899387.085643c-.08599592.0570954-.12201369.1427384-.10805331.2569291 0 .1141907.01786928.210643.05360784.289357.03573856.0787139.07538603.125.1189424.138858l.06449694.0432373h10.71905575v-2.9542683zm-4.3991936 3.8106985h-.8191949v2.077051h-9.8563045c0 .0144124-.02149898.0144124-.06449694 0-.04299795-.0144125-.08962561.0105321-.13988296.0748337-.05025735.0643015-.07538603.1607538-.07538603.289357 0 .1141906.02149898.2070399.06449694.2785476.04299795.0715078.08599591.1141907.12899387.1280488l.06449693.0216186h10.69811519v-2.8686252z" />
9
- </g>
10
- </svg>
11
- `;