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

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 (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 +583 -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
- `;