@internetarchive/bookreader 5.0.0-35 → 5.0.0-38
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.
- package/.eslintrc.js +1 -11
- package/.github/workflows/node.js.yml +69 -7
- package/.github/workflows/npm-publish.yml +2 -16
- package/BookReader/BookReader.js +1 -1
- package/BookReader/BookReader.js.LICENSE.txt +8 -29
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/ia-bookreader-bundle.js +100 -99
- package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +15 -12
- package/BookReader/ia-bookreader-bundle.js.map +1 -1
- package/BookReader/plugins/plugin.chapters.js +1 -1
- package/BookReader/plugins/plugin.chapters.js.map +1 -1
- package/BookReader/plugins/plugin.search.js +1 -1
- package/BookReader/plugins/plugin.search.js.map +1 -1
- package/BookReader/plugins/plugin.text_selection.js +1 -1
- package/BookReader/plugins/plugin.text_selection.js.map +1 -1
- package/BookReader/plugins/plugin.tts.js +1 -1
- package/BookReader/plugins/plugin.tts.js.map +1 -1
- package/CHANGELOG.md +28 -0
- package/README.md +1 -1
- package/codecov.yml +6 -0
- package/package.json +18 -21
- package/renovate.json +43 -0
- package/src/BookNavigator/assets/bookmark-colors.js +1 -1
- package/src/BookNavigator/assets/button-base.js +1 -1
- package/src/BookNavigator/assets/ia-logo.js +1 -1
- package/src/BookNavigator/assets/icon_checkmark.js +1 -1
- package/src/BookNavigator/assets/icon_close.js +1 -1
- package/src/BookNavigator/assets/icon_sort_asc.js +1 -1
- package/src/BookNavigator/assets/icon_sort_desc.js +1 -1
- package/src/BookNavigator/assets/icon_sort_neutral.js +1 -1
- package/src/BookNavigator/assets/icon_volumes.js +1 -1
- package/src/BookNavigator/book-navigator.js +8 -3
- package/src/BookNavigator/bookmarks/bookmark-button.js +1 -1
- package/src/BookNavigator/bookmarks/bookmark-edit.js +2 -3
- package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
- package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +1 -1
- package/src/BookNavigator/bookmarks/bookmarks-provider.js +1 -1
- package/src/BookNavigator/bookmarks/ia-bookmarks.js +30 -34
- package/src/BookNavigator/delete-modal-actions.js +1 -1
- package/src/BookNavigator/downloads/downloads-provider.js +1 -1
- package/src/BookNavigator/downloads/downloads.js +1 -2
- package/src/BookNavigator/search/a-search-result.js +2 -3
- package/src/BookNavigator/search/search-provider.js +3 -4
- package/src/BookNavigator/search/search-results.js +1 -2
- package/src/BookNavigator/sharing.js +1 -1
- package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +1 -1
- package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
- package/src/BookNavigator/volumes/volumes-provider.js +1 -1
- package/src/BookNavigator/volumes/volumes.js +2 -3
- package/src/BookReader/Mode1Up.js +2 -1
- package/src/BookReader/Mode1UpLit.js +3 -2
- package/src/BookReader.js +59 -57
- package/src/ia-bookreader/ia-bookreader.js +5 -2
- package/src/plugins/plugin.chapters.js +11 -15
- package/src/plugins/plugin.text_selection.js +9 -10
- package/src/plugins/search/plugin.search.js +8 -18
- package/src/plugins/search/view.js +2 -0
- package/src/plugins/tts/AbstractTTSEngine.js +40 -38
- package/src/plugins/tts/FestivalTTSEngine.js +10 -11
- package/src/plugins/tts/PageChunk.js +11 -20
- package/src/plugins/tts/PageChunkIterator.js +8 -12
- package/src/plugins/tts/WebTTSEngine.js +59 -68
- package/src/plugins/tts/plugin.tts.js +16 -10
- package/stat/BookNavigator/BookNavigator.js +42 -0
- package/tests/e2e/base.test.js +2 -0
- package/tests/e2e/helpers/desktopSearch.js +13 -12
- package/tests/e2e/helpers/params.js +1 -1
- package/tests/e2e/models/Navigation.js +12 -3
- package/tests/e2e/rightToLeft.test.js +1 -1
- package/tests/e2e/viewmode.test.js +42 -36
- package/tests/jest/BookReader/Mode1UpLit.test.js +2 -1
- package/tests/jest/plugins/plugin.text_selection.test.js +25 -23
- package/tests/jest/plugins/search/plugin.search.test.js +12 -20
- package/tests/jest/plugins/tts/AbstractTTSEngine.test.js +3 -3
- package/tests/karma/BookNavigator/bookmarks/bookmarks-list.test.js +2 -2
- package/tests/karma/BookNavigator/downloads/downloads.test.js +1 -1
- package/tests/karma/BookNavigator/volumes/volumes-provider.test.js +3 -3
- package/webpack.config.js +1 -1
- package/.github/dependabot.yml +0 -8
- 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
|
-
|
|
97
|
-
|
|
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)
|
|
127
|
-
|
|
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
|
-
|
|
133
|
+
async play() {
|
|
134
|
+
await new Promise(res => {
|
|
136
135
|
this._finishResolver = res;
|
|
137
136
|
this.sound.play({ onfinish: res });
|
|
138
|
-
})
|
|
139
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
64
|
-
.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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()
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
333
|
-
|
|
334
|
-
|
|
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
|
package/tests/e2e/base.test.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
await t.
|
|
29
|
-
|
|
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.
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
await t.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
//
|
|
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
|
-
|
|
38
|
+
mode.requestUpdate();
|
|
39
|
+
await mode.updateComplete;
|
|
39
40
|
expect(mode.pageTops).toEqual({});
|
|
40
41
|
});
|
|
41
42
|
|