@internetarchive/bookreader 5.0.0-35 → 5.0.0-38

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. package/.eslintrc.js +1 -11
  2. package/.github/workflows/node.js.yml +69 -7
  3. package/.github/workflows/npm-publish.yml +2 -16
  4. package/BookReader/BookReader.js +1 -1
  5. package/BookReader/BookReader.js.LICENSE.txt +8 -29
  6. package/BookReader/BookReader.js.map +1 -1
  7. package/BookReader/ia-bookreader-bundle.js +100 -99
  8. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +15 -12
  9. package/BookReader/ia-bookreader-bundle.js.map +1 -1
  10. package/BookReader/plugins/plugin.chapters.js +1 -1
  11. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  12. package/BookReader/plugins/plugin.search.js +1 -1
  13. package/BookReader/plugins/plugin.search.js.map +1 -1
  14. package/BookReader/plugins/plugin.text_selection.js +1 -1
  15. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  16. package/BookReader/plugins/plugin.tts.js +1 -1
  17. package/BookReader/plugins/plugin.tts.js.map +1 -1
  18. package/CHANGELOG.md +28 -0
  19. package/README.md +1 -1
  20. package/codecov.yml +6 -0
  21. package/package.json +18 -21
  22. package/renovate.json +43 -0
  23. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  24. package/src/BookNavigator/assets/button-base.js +1 -1
  25. package/src/BookNavigator/assets/ia-logo.js +1 -1
  26. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  27. package/src/BookNavigator/assets/icon_close.js +1 -1
  28. package/src/BookNavigator/assets/icon_sort_asc.js +1 -1
  29. package/src/BookNavigator/assets/icon_sort_desc.js +1 -1
  30. package/src/BookNavigator/assets/icon_sort_neutral.js +1 -1
  31. package/src/BookNavigator/assets/icon_volumes.js +1 -1
  32. package/src/BookNavigator/book-navigator.js +8 -3
  33. package/src/BookNavigator/bookmarks/bookmark-button.js +1 -1
  34. package/src/BookNavigator/bookmarks/bookmark-edit.js +2 -3
  35. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  36. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +1 -1
  37. package/src/BookNavigator/bookmarks/bookmarks-provider.js +1 -1
  38. package/src/BookNavigator/bookmarks/ia-bookmarks.js +30 -34
  39. package/src/BookNavigator/delete-modal-actions.js +1 -1
  40. package/src/BookNavigator/downloads/downloads-provider.js +1 -1
  41. package/src/BookNavigator/downloads/downloads.js +1 -2
  42. package/src/BookNavigator/search/a-search-result.js +2 -3
  43. package/src/BookNavigator/search/search-provider.js +3 -4
  44. package/src/BookNavigator/search/search-results.js +1 -2
  45. package/src/BookNavigator/sharing.js +1 -1
  46. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +1 -1
  47. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
  48. package/src/BookNavigator/volumes/volumes-provider.js +1 -1
  49. package/src/BookNavigator/volumes/volumes.js +2 -3
  50. package/src/BookReader/Mode1Up.js +2 -1
  51. package/src/BookReader/Mode1UpLit.js +3 -2
  52. package/src/BookReader.js +59 -57
  53. package/src/ia-bookreader/ia-bookreader.js +5 -2
  54. package/src/plugins/plugin.chapters.js +11 -15
  55. package/src/plugins/plugin.text_selection.js +9 -10
  56. package/src/plugins/search/plugin.search.js +8 -18
  57. package/src/plugins/search/view.js +2 -0
  58. package/src/plugins/tts/AbstractTTSEngine.js +40 -38
  59. package/src/plugins/tts/FestivalTTSEngine.js +10 -11
  60. package/src/plugins/tts/PageChunk.js +11 -20
  61. package/src/plugins/tts/PageChunkIterator.js +8 -12
  62. package/src/plugins/tts/WebTTSEngine.js +59 -68
  63. package/src/plugins/tts/plugin.tts.js +16 -10
  64. package/stat/BookNavigator/BookNavigator.js +42 -0
  65. package/tests/e2e/base.test.js +2 -0
  66. package/tests/e2e/helpers/desktopSearch.js +13 -12
  67. package/tests/e2e/helpers/params.js +1 -1
  68. package/tests/e2e/models/Navigation.js +12 -3
  69. package/tests/e2e/rightToLeft.test.js +1 -1
  70. package/tests/e2e/viewmode.test.js +42 -36
  71. package/tests/jest/BookReader/Mode1UpLit.test.js +2 -1
  72. package/tests/jest/plugins/plugin.text_selection.test.js +25 -23
  73. package/tests/jest/plugins/search/plugin.search.test.js +12 -20
  74. package/tests/jest/plugins/tts/AbstractTTSEngine.test.js +3 -3
  75. package/tests/karma/BookNavigator/bookmarks/bookmarks-list.test.js +2 -2
  76. package/tests/karma/BookNavigator/downloads/downloads.test.js +1 -1
  77. package/tests/karma/BookNavigator/volumes/volumes-provider.test.js +3 -3
  78. package/webpack.config.js +1 -1
  79. package/.github/dependabot.yml +0 -8
  80. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
@@ -91,10 +91,10 @@ export default class FestivalTTSEngine extends AbstractTTSEngine {
91
91
  * See https://stackoverflow.com/questions/12206631/html5-audio-cant-play-through-javascript-unless-triggered-manually-once
92
92
  * @return {PromiseLike}
93
93
  */
94
- iOSCaptureUserIntentHack() {
94
+ async iOSCaptureUserIntentHack() {
95
95
  const sound = soundManager.createSound({ url: SILENCE_1MS[this.audioFormat] });
96
- return new Promise(res => sound.play({onfinish: res}))
97
- .then(() => sound.destruct());
96
+ await new Promise(res => sound.play({onfinish: res}));
97
+ sound.destruct();
98
98
  }
99
99
  }
100
100
 
@@ -122,21 +122,20 @@ class FestivalTTSSound {
122
122
  if (this.rate != 1) this.sound.setPlaybackRate(this.rate);
123
123
  onload();
124
124
  },
125
- onresume: () => {
126
- sleep(25).then(() => {
127
- if (this.rate != 1) this.sound.setPlaybackRate(this.rate);
128
- });
125
+ onresume: async () => {
126
+ await sleep(25);
127
+ if (this.rate != 1) this.sound.setPlaybackRate(this.rate);
129
128
  }
130
129
  });
131
130
  return this.sound.load();
132
131
  }
133
132
 
134
- play() {
135
- return new Promise(res => {
133
+ async play() {
134
+ await new Promise(res => {
136
135
  this._finishResolver = res;
137
136
  this.sound.play({ onfinish: res });
138
- })
139
- .then(() => this.sound.destruct());
137
+ });
138
+ this.sound.destruct();
140
139
  }
141
140
 
142
141
  /** @override */
@@ -21,27 +21,18 @@ export default class PageChunk {
21
21
  * @param {number} leafIndex
22
22
  * @return {Promise<PageChunk[]>}
23
23
  */
24
- static fetch(server, bookPath, leafIndex) {
25
- // jquery's ajax "PromiseLike" implementation is inconsistent with
26
- // modern Promises, so convert it to a full promise (it doesn't forward
27
- // a returned promise to the next handler in the chain, which kind of
28
- // defeats the entire point of using promises to avoid "callback hell")
29
- return new Promise((res, rej) => {
30
- $.ajax({
31
- type: 'GET',
32
- url: `https://${server}/BookReader/BookReaderGetTextWrapper.php`,
33
- dataType:'jsonp',
34
- cache: true,
35
- data: {
36
- path: `${bookPath}_djvu.xml`,
37
- page: leafIndex
38
- },
39
- error: rej,
40
- })
41
- .then(chunks => {
42
- res(PageChunk._fromTextWrapperResponse(leafIndex, chunks));
43
- });
24
+ static async fetch(server, bookPath, leafIndex) {
25
+ const chunks = await $.ajax({
26
+ type: 'GET',
27
+ url: `https://${server}/BookReader/BookReaderGetTextWrapper.php`,
28
+ dataType:'jsonp',
29
+ cache: true,
30
+ data: {
31
+ path: `${bookPath}_djvu.xml`,
32
+ page: leafIndex
33
+ }
44
34
  });
35
+ return PageChunk._fromTextWrapperResponse(leafIndex, chunks);
45
36
  }
46
37
 
47
38
  /**
@@ -53,22 +53,18 @@ export default class PageChunkIterator {
53
53
  * in the correct order.
54
54
  * @return {PromiseLike<"__PageChunkIterator.AT_END__" | PageChunk>}
55
55
  */
56
- _nextUncontrolled() {
56
+ async _nextUncontrolled() {
57
57
  if (this._cursor.page == this.pageCount) {
58
58
  return Promise.resolve(PageChunkIterator.AT_END);
59
59
  }
60
-
61
60
  this._recenterBuffer(this._cursor.page);
62
-
63
- return this._fetchPageChunks(this._cursor.page)
64
- .then(chunks => {
65
- if (this._cursor.chunk == chunks.length) {
66
- this._cursor.page++;
67
- this._cursor.chunk = 0;
68
- return this._nextUncontrolled();
69
- }
70
- return chunks[this._cursor.chunk++];
71
- });
61
+ const chunks = await this._fetchPageChunks(this._cursor.page);
62
+ if (this._cursor.chunk == chunks.length) {
63
+ this._cursor.page++;
64
+ this._cursor.chunk = 0;
65
+ return this._nextUncontrolled();
66
+ }
67
+ return chunks[this._cursor.chunk++];
72
68
  }
73
69
 
74
70
  /**
@@ -167,7 +167,7 @@ export class WebTTSSound {
167
167
  * left off.
168
168
  * @return {Promise<void>}
169
169
  */
170
- reload() {
170
+ async reload() {
171
171
  // We'll restore the pause state, so copy it here
172
172
  const wasPaused = this.paused;
173
173
  // Use recent event to determine where we'll restart from
@@ -179,14 +179,12 @@ export class WebTTSSound {
179
179
  }
180
180
 
181
181
  // We can't modify the utterance object, so we have to make a new one
182
- return this.stop()
183
- .then(() => {
184
- this.load();
185
- // Instead of playing and immediately pausing, we don't start playing. Note
186
- // this is a requirement because pause doesn't work consistently across
187
- // browsers.
188
- if (!wasPaused) this.play();
189
- });
182
+ await this.stop();
183
+ this.load();
184
+ // Instead of playing and immediately pausing, we don't start playing. Note
185
+ // this is a requirement because pause doesn't work consistently across
186
+ // browsers.
187
+ if (!wasPaused) this.play();
190
188
  }
191
189
 
192
190
  play() {
@@ -222,15 +220,16 @@ export class WebTTSSound {
222
220
  return endPromise;
223
221
  }
224
222
 
225
- finish() {
226
- this.stop().then(() => this.utterance.dispatchEvent(new Event('finish')));
223
+ async finish() {
224
+ await this.stop();
225
+ this.utterance.dispatchEvent(new Event('finish'));
227
226
  }
228
227
 
229
228
  /**
230
229
  * @override
231
230
  * Will fire a pause event unless already paused
232
231
  **/
233
- pause() {
232
+ async pause() {
234
233
  if (this.paused) return;
235
234
 
236
235
  const pausePromise = promisifyEvent(this.utterance, 'pause');
@@ -246,19 +245,18 @@ export class WebTTSSound {
246
245
  if (pauseMightNotFire) {
247
246
  // wait for it just in case
248
247
  const timeoutPromise = sleep(100).then(() => 'timeout');
249
- Promise.race([pausePromise, timeoutPromise])
250
- .then(result => {
251
- // We got our pause event; nothing to do!
252
- if (result != 'timeout') return;
253
-
254
- this.utterance.dispatchEvent(new CustomEvent('pause', this._lastEvents.start));
255
- // if pause might not work, then we'll stop entirely and restart later
256
- if (pauseMightNotWork) this.stop();
257
- });
248
+ const result = await Promise.race([pausePromise, timeoutPromise]);
249
+ // We got our pause event; nothing to do!
250
+ if (result != 'timeout') return;
251
+
252
+ this.utterance.dispatchEvent(new CustomEvent('pause', this._lastEvents.start));
253
+
254
+ // if pause might not work, then we'll stop entirely and restart later
255
+ if (pauseMightNotWork) this.stop();
258
256
  }
259
257
  }
260
258
 
261
- resume() {
259
+ async resume() {
262
260
  if (!this.started) {
263
261
  this.play();
264
262
  return;
@@ -278,16 +276,15 @@ export class WebTTSSound {
278
276
  speechSynthesis.resume();
279
277
 
280
278
  if (resumeMightNotFire) {
281
- Promise.race([resumePromise, sleep(100).then(() => 'timeout')])
282
- .then(result => {
283
- if (result != 'timeout') return;
284
-
285
- this.utterance.dispatchEvent(new CustomEvent('resume', {}));
286
- if (resumeMightNotWork) {
287
- const reloadPromise = this.reload();
288
- reloadPromise.then(() => this.play());
289
- }
290
- });
279
+ const result = await Promise.race([resumePromise, sleep(100).then(() => 'timeout')]);
280
+
281
+ if (result != 'timeout') return;
282
+
283
+ this.utterance.dispatchEvent(new CustomEvent('resume', {}));
284
+ if (resumeMightNotWork) {
285
+ await this.reload();
286
+ this.play();
287
+ }
291
288
  }
292
289
  }
293
290
 
@@ -308,45 +305,39 @@ export class WebTTSSound {
308
305
  * We avoid this (as described here: https://bugs.chromium.org/p/chromium/issues/detail?id=679437#c15 )
309
306
  * by pausing after 14 seconds and ~instantly resuming.
310
307
  */
311
- _chromePausingBugFix() {
308
+ async _chromePausingBugFix() {
312
309
  const timeoutPromise = sleep(14000).then(() => 'timeout');
313
310
  const pausePromise = promisifyEvent(this.utterance, 'pause').then(() => 'paused');
314
311
  const endPromise = promisifyEvent(this.utterance, 'end').then(() => 'ended');
315
- return Promise.race([timeoutPromise, pausePromise, endPromise])
316
- .then(result => {
317
- if (location.toString().indexOf('_debugReadAloud=true') != -1) {
318
- console.log(`CHROME-PAUSE-HACK: ${result}`);
319
- }
320
- switch (result) {
321
- case 'ended':
322
- // audio was stopped/finished; nothing to do
323
- break;
324
- case 'paused':
325
- // audio was paused; wait for resume
326
- // Chrome won't let you resume the audio if 14s have passed 🤷‍
327
- // We could do the same as before (but resume+pause instead of pause+resume),
328
- // but that means we'd _constantly_ be running in the background. So in that
329
- // case, let's just restart the chunk
330
- Promise.race([
331
- promisifyEvent(this.utterance, 'resume'),
332
- sleep(14000).then(() => 'timeout'),
333
- ])
334
- .then(result => {
335
- result == 'timeout' ? this.reload() : this._chromePausingBugFix();
336
- });
337
- break;
338
- case 'timeout':
339
- // We hit Chrome's secret cut off time. Pause/resume
340
- // to be able to keep TTS-ing
341
- speechSynthesis.pause();
342
- sleep(25)
343
- .then(() => {
344
- speechSynthesis.resume();
345
- this._chromePausingBugFix();
346
- });
347
- break;
348
- }
349
- });
312
+ const result = await Promise.race([timeoutPromise, pausePromise, endPromise]);
313
+ if (location.toString().indexOf('_debugReadAloud=true') != -1) {
314
+ console.log(`CHROME-PAUSE-HACK: ${result}`);
315
+ }
316
+ switch (result) {
317
+ case 'ended':
318
+ // audio was stopped/finished; nothing to do
319
+ break;
320
+ case 'paused':
321
+ // audio was paused; wait for resume
322
+ // Chrome won't let you resume the audio if 14s have passed 🤷‍
323
+ // We could do the same as before (but resume+pause instead of pause+resume),
324
+ // but that means we'd _constantly_ be running in the background. So in that
325
+ // case, let's just restart the chunk
326
+ await Promise.race([
327
+ promisifyEvent(this.utterance, 'resume'),
328
+ sleep(14000).then(() => 'timeout'),
329
+ ]);
330
+ result == 'timeout' ? this.reload() : this._chromePausingBugFix();
331
+ break;
332
+ case 'timeout':
333
+ // We hit Chrome's secret cut off time. Pause/resume
334
+ // to be able to keep TTS-ing
335
+ speechSynthesis.pause();
336
+ await sleep(25);
337
+ speechSynthesis.resume();
338
+ this._chromePausingBugFix();
339
+ break;
340
+ }
350
341
  }
351
342
  }
352
343
 
@@ -261,9 +261,7 @@ BookReader.prototype.ttsStop = function () {
261
261
  BookReader.prototype.ttsBeforeChunkPlay = async function(chunk) {
262
262
  await this.ttsMaybeFlipToIndex(chunk.leafIndex);
263
263
  this.ttsHighlightChunk(chunk);
264
- // This appears not to work; ttsMaybeFlipToIndex causes a scroll to the top of
265
- // the active page :/ Disabling cause the extra scroll just adds an odd jitter.
266
- // this.ttsScrollToChunk(chunk);
264
+ this.ttsScrollToChunk(chunk);
267
265
  };
268
266
 
269
267
  /**
@@ -292,10 +290,7 @@ BookReader.prototype.ttsMaybeFlipToIndex = function (leafIndex) {
292
290
  resolve();
293
291
  } else {
294
292
  this.animationFinishedCallback = resolve;
295
- const mustGoNext = leafIndex > Math.max(this.twoPage.currentIndexR, this.twoPage.currentIndexL);
296
- if (mustGoNext) this.next();
297
- else this.prev();
298
- promise.then(this.ttsMaybeFlipToIndex.bind(this, leafIndex));
293
+ this.jumpToIndex(leafIndex);
299
294
  }
300
295
  }
301
296
 
@@ -329,9 +324,20 @@ BookReader.prototype.ttsHighlightChunk = function(chunk) {
329
324
  * @param {PageChunk} chunk
330
325
  */
331
326
  BookReader.prototype.ttsScrollToChunk = function(chunk) {
332
- if (this.constMode1up != this.mode) return;
333
-
334
- $(`.pagediv${chunk.leafIndex} .ttsHiliteLayer rect`)[0]?.scrollIntoView();
327
+ // It behaves weird if used in thumb mode
328
+ if (this.constModeThumb == this.mode) return;
329
+
330
+ $(`.pagediv${chunk.leafIndex} .ttsHiliteLayer rect`).last()?.[0]?.scrollIntoView({
331
+ // Only vertically center the highlight if we're in 1up or in full screen. In
332
+ // 2up, if we're not fullscreen, the whole body gets scrolled around to try to
333
+ // center the highlight 🙄 See:
334
+ // https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move/11041376
335
+ // Note: nearest doesn't quite work great, because the ReadAloud toolbar is now
336
+ // full-width, and covers up the last line of the highlight.
337
+ block: this.constMode1up == this.mode || this.isFullscreenActive ? 'center' : 'nearest',
338
+ inline: 'center',
339
+ behavior: 'smooth',
340
+ });
335
341
  };
336
342
 
337
343
  // ttsRemoveHilites()
@@ -1,3 +1,44 @@
1
+ /*
2
+ <script src="foo-plugin.js"></script>
3
+
4
+ foo-plugin.js
5
+
6
+ $('#foo-plugin').dataSet('MainController', 'bar');
7
+
8
+ <ia-bookreader>
9
+ <div slot="plugins">
10
+ <ia-search></ia-search>
11
+ <book-marks></book-marks>
12
+ </div>
13
+ </ia-bookreader>
14
+
15
+ iaBookreader.registerPlugin('foo-plugin', Class);
16
+
17
+
18
+ class IABr extends LItElement {
19
+
20
+ render() {
21
+
22
+ registerPlugins() {
23
+ this.pluginSlots.map(slot => {
24
+ .. to slot registry
25
+ each slot - do handshake
26
+
27
+
28
+ });
29
+ }
30
+
31
+ html`
32
+ <div slot="plugins" @onslotchange=${() => x}></div>
33
+ `;
34
+ }
35
+ }
36
+ */
37
+
38
+
39
+
40
+
41
+
1
42
  import { css, html, LitElement } from 'lit-element';
2
43
  import { SharedResizeObserver } from '@internetarchive/shared-resize-observer';
3
44
  import SearchProvider from './search/search-provider.js';
@@ -113,6 +154,7 @@ export class BookNavigator extends LitElement {
113
154
  // };
114
155
 
115
156
  this.menuProviders = {
157
+ // if enableSearch ?
116
158
  search: new SearchProvider(
117
159
  /**
118
160
  * Search specific menu updates
@@ -21,10 +21,12 @@ ocaids.forEach(ocaid => {
21
21
  fixture `Base Tests for: ${ocaid}`.page `${url}`;
22
22
  runBaseTests(new BookReader());
23
23
 
24
+
24
25
  fixture `Desktop Search Tests for: ${ocaid}`
25
26
  .page `${url}`;
26
27
  runDesktopSearchTests(new BookReader());
27
28
 
29
+ // Todo: deprecated, will remove once mmenu is removed.
28
30
  // fixture `Mobile Search Tests for: ${ocaid}`
29
31
  // .page `${url}`
30
32
  // runMobileSearchTests(new BookReader());
@@ -19,14 +19,15 @@ export function runDesktopSearchTests(br) {
19
19
  const nav = br.nav;
20
20
 
21
21
  //assuring that the search bar is enabled
22
- await t.expect(nav.desktop.searchBox.visible).ok();
22
+ await t.expect(nav.desktop.searchIcon.visible).ok();
23
+ await t.click(nav.desktop.searchIcon);
23
24
 
24
25
  //testing search for a word found in the book
25
- await t
26
- .selectText(nav.desktop.searchBox.child('.BRsearchInput'))
27
- .pressKey('delete');
28
- await t.typeText(nav.desktop.searchBox.child('.BRsearchInput'), TEST_TEXT_FOUND);
29
- await t.click((nav.desktop.searchBox).child('.BRsearchSubmit'));
26
+ await t.selectText(nav.desktop.searchBox).pressKey('delete');
27
+ // FIXME: Why is it only typing every other letter?!?!
28
+ await t.typeText(nav.desktop.searchBox, TEST_TEXT_FOUND.split('').join('_'));
29
+ await t.pressKey('enter');
30
+
30
31
  await t.expect(nav.desktop.searchPin.exists).ok();
31
32
  await t.expect(nav.desktop.searchPin.child('.BRquery').child('div').exists).ok();
32
33
  await t.expect(nav.desktop.searchPin.child('.BRquery').child('div').innerText).contains(TEST_TEXT_FOUND);
@@ -54,14 +55,14 @@ export function runDesktopSearchTests(br) {
54
55
  const nav = br.nav;
55
56
 
56
57
  //assuring that the search bar is enabled
57
- await t.expect(nav.desktop.searchBox.visible).ok();
58
+ await t.expect(nav.desktop.searchIcon.visible).ok();
59
+ await t.click(nav.desktop.searchIcon);
58
60
 
59
61
  //testing search for a word not found in the book
60
- await t
61
- .selectText(nav.desktop.searchBox.child('.BRsearchInput'))
62
- .pressKey('delete');
63
- await t.typeText(nav.desktop.searchBox.child('.BRsearchInput'), TEST_TEXT_NOT_FOUND);
64
- await t.click((nav.desktop.searchBox).child('.BRsearchSubmit'));
62
+ await t.selectText(nav.desktop.searchBox).pressKey('delete');
63
+ // FIXME: Why is it only typing every other letter?!?!
64
+ await t.typeText(nav.desktop.searchBox, TEST_TEXT_NOT_FOUND.split('').join('_'));
65
+ await t.pressKey('enter');
65
66
  await t.expect(nav.desktop.searchPin.child('.BRquery').child('div').withText(TEST_TEXT_NOT_FOUND).exists).notOk();
66
67
 
67
68
  const getPageUrl = ClientFunction(() => window.location.href.toString());
@@ -1,7 +1,7 @@
1
1
  // @ts-check
2
2
  class TestParams {
3
3
  baseUrl = process.env.BASE_URL?.replace(/\/+$/, '') ?? 'http://127.0.0.1:8000'
4
- ocaids = process.env.OCAIDS?.split(',') ?? [];
4
+ ocaids = process.env.OCAIDS?.split(',') ?? null;
5
5
  /** Whether the url we're testing is a prod (or near prod) IA url, or a demos url */
6
6
  isIA = new URL(this.baseUrl).hostname.endsWith('archive.org');
7
7
 
@@ -6,7 +6,8 @@ export default class Navigation {
6
6
  this.topNavShell = new Selector('.BRtoolbar');
7
7
  this.bottomNavShell = new Selector('.BRfooter');
8
8
  this.mobileMenu = new Selector('.BRmobileMenu');
9
- this.desktop = new DesktopNav(this.bottomNavShell, this.topNavShell);
9
+ this.itemNav = Selector('ia-bookreader').shadowRoot().find('ia-item-navigator').shadowRoot();
10
+ this.desktop = new DesktopNav(this.bottomNavShell, this.itemNav);
10
11
  this.mobile = new MobileNav(this.mobileMenu, this.topNavShell);
11
12
  }
12
13
  }
@@ -17,7 +18,11 @@ export default class Navigation {
17
18
  * @classdesc defines DesktopNav base elements
18
19
  */
19
20
  class DesktopNav {
20
- constructor(bottomToolbar, topToolbar) {
21
+ /**
22
+ * @param {Selector} bottomToolbar
23
+ * @param {Selector} itemNav
24
+ */
25
+ constructor(bottomToolbar, itemNav) {
21
26
  // flipping
22
27
  this.goLeft = bottomToolbar.find('.BRicon.book_left');
23
28
  this.goRight = bottomToolbar.find('.BRicon.book_right');
@@ -35,7 +40,11 @@ class DesktopNav {
35
40
  this.zoomOut = bottomToolbar.find('.BRicon.zoom_out');
36
41
 
37
42
  // search
38
- this.searchBox = topToolbar.find('.BRbooksearch.desktop');
43
+ this.searchIcon = itemNav.find('button.shortcut.search');
44
+ this.searchBox = itemNav
45
+ .find('ia-menu-slider').shadowRoot()
46
+ .find('ia-book-search-results').shadowRoot()
47
+ .find('input[name=query]');
39
48
  this.searchPin = bottomToolbar.find('.BRsearch');
40
49
  this.searchNavigation = bottomToolbar.find('.BRsearch-navigation');
41
50
 
@@ -9,7 +9,7 @@ const ocaids = params.ocaids || [
9
9
  ];
10
10
 
11
11
  ocaids.forEach(ocaid => {
12
- const url = `${params.baseUrl}/BookReaderDemo/demo-internetarchive.html?ocaid=${ocaid}`;
12
+ const url = `${params.getArchiveUrl(ocaid)}`;
13
13
 
14
14
  fixture `Base Tests for right to left book: ${ocaid}`.page `${url}`;
15
15
  runBaseTests(new BookReader({ pageProgression: 'rl' }));
@@ -1,36 +1,42 @@
1
- // import { Selector } from 'testcafe';
2
- // import BookReader from './models/BookReader';
3
- // import params from './helpers/params';
4
-
5
- // fixture `Viewmode carousel`.page `${params.baseUrl}/BookReaderDemo/viewmode-cycle.html`;
6
-
7
- // test('Clicking `view mode` cycles through view modes', async t => {
8
- // const { nav } = (new BookReader());
9
-
10
- // // viewmode button only appear on mobile devices
11
- // await t.resizeWindow(400, 800);
12
- // // Flip forward one
13
- // await t.pressKey('right');
14
-
15
- // // 2up to thumb
16
- // await t.click(nav.desktop.viewmode);
17
- // const thumbnailContainer = Selector('.BRmodeThumb');
18
- // await t.expect(thumbnailContainer.visible).ok();
19
- // const thumbImages = thumbnailContainer.find('.BRpageview img');
20
- // await t.expect(thumbImages.count).gt(0);
21
-
22
- // // thumb to 1up
23
- // await t.click(nav.desktop.viewmode);
24
- // const onePageViewContainer = Selector('br-mode-1up');
25
- // await t.expect(onePageViewContainer.visible).ok();
26
- // const onePageImages = onePageViewContainer.find('.BRmode1up .BRpagecontainer');
27
- // // we usually pre-fetch the page in question & 1 before/after it
28
- // await t.expect(onePageImages.count).gte(3);
29
-
30
- // // 1up to 2up
31
- // await t.click(nav.desktop.viewmode);
32
- // const twoPageContainer = Selector('.BRtwopageview');
33
- // await t.expect(twoPageContainer.visible).ok();
34
- // const twoPageImages = twoPageContainer.find('img.BRpageimage');
35
- // await t.expect(twoPageImages.count).gte(2);
36
- // });
1
+ import { Selector } from 'testcafe';
2
+ import BookReader from './models/BookReader';
3
+ import params from './helpers/params';
4
+
5
+ const ocaids = params.ocaids || ['goody'];
6
+
7
+ ocaids.forEach(ocaid => {
8
+ const url = params.getArchiveUrl(ocaid);
9
+
10
+ fixture `Viewmode carousel`.page `${url}`;
11
+
12
+ test('Clicking `view mode` cycles through view modes', async t => {
13
+ const { nav } = (new BookReader());
14
+
15
+ // viewmode button only appear on mobile devices
16
+ await t.resizeWindow(400, 800);
17
+ // Flip forward one
18
+ await t.pressKey('right');
19
+
20
+ // 2up to thumb
21
+ await t.click(nav.desktop.viewmode);
22
+ const thumbnailContainer = Selector('.BRmodeThumb');
23
+ await t.expect(thumbnailContainer.visible).ok();
24
+ const thumbImages = thumbnailContainer.find('.BRpageview img');
25
+ await t.expect(thumbImages.count).gt(0);
26
+
27
+ // thumb to 1up
28
+ await t.click(nav.desktop.viewmode);
29
+ const onePageViewContainer = Selector('br-mode-1up');
30
+ await t.expect(onePageViewContainer.visible).ok();
31
+ const onePageImages = onePageViewContainer.find('.BRmode1up .BRpagecontainer');
32
+ // we usually pre-fetch the page in question & 1 before/after it
33
+ await t.expect(onePageImages.count).gte(3);
34
+
35
+ // 1up to 2up
36
+ await t.click(nav.desktop.viewmode);
37
+ const twoPageContainer = Selector('.BRtwopageview');
38
+ await t.expect(twoPageContainer.visible).ok();
39
+ const twoPageImages = twoPageContainer.find('img.BRpageimage');
40
+ await t.expect(twoPageImages.count).gte(2);
41
+ });
42
+ });
@@ -35,7 +35,8 @@ describe('pageTops', () => {
35
35
  const book = new BookModel(br);
36
36
  const mode = new Mode1UpLit(book, br);
37
37
  document.body.appendChild(mode);
38
- await mode.requestUpdate();
38
+ mode.requestUpdate();
39
+ await mode.updateComplete;
39
40
  expect(mode.pageTops).toEqual({});
40
41
  });
41
42