@internetarchive/bookreader 5.0.0-90 → 5.0.0-92
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/BookReader/BookReader.js +1 -1
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/ia-bookreader-bundle.js +2 -2
- package/BookReader/ia-bookreader-bundle.js.map +1 -1
- package/BookReader/plugins/plugin.archive_analytics.js +1 -1
- package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
- package/BookReader/plugins/plugin.autoplay.js +1 -1
- package/BookReader/plugins/plugin.autoplay.js.map +1 -1
- package/BookReader/plugins/plugin.chapters.js +2 -2
- package/BookReader/plugins/plugin.chapters.js.map +1 -1
- package/BookReader/plugins/plugin.iiif.js +1 -1
- package/BookReader/plugins/plugin.iiif.js.map +1 -1
- package/BookReader/plugins/plugin.resume.js +1 -1
- package/BookReader/plugins/plugin.resume.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/BookReaderDemo/IADemoBr.js +29 -1
- package/BookReaderDemo/ia-multiple-volumes-manifest.js +0 -1
- package/CHANGELOG.md +28 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/BookNavigator/book-navigator.js +5 -2
- package/src/BookNavigator/search/search-provider.js +13 -7
- package/src/BookNavigator/sharing.js +1 -1
- package/src/BookReader/BookModel.js +5 -4
- package/src/BookReader/Toolbar/Toolbar.js +5 -0
- package/src/BookReader/options.js +10 -6
- package/src/BookReader.js +49 -23
- package/src/BookReaderPlugin.js +8 -0
- package/src/plugins/plugin.chapters.js +220 -157
- package/src/plugins/plugin.text_selection.js +19 -1
- package/src/plugins/search/plugin.search.js +330 -376
- package/src/plugins/search/view.js +13 -9
- package/src/plugins/tts/WebTTSEngine.js +67 -41
- package/src/plugins/tts/plugin.tts.js +1 -3
- package/src/plugins/tts/utils.js +13 -0
- package/src/util/browserSniffing.js +11 -1
- package/tests/e2e/helpers/mockSearch.js +1 -1
- package/tests/jest/BookNavigator/book-navigator.test.js +8 -3
- package/tests/jest/BookNavigator/search/search-provider.test.js +16 -4
- package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +1 -1
- package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +70 -0
- package/tests/jest/BookReader.test.js +26 -1
- package/tests/jest/plugins/plugin.chapters.test.js +56 -58
- package/tests/jest/plugins/search/plugin.search.test.js +17 -42
- package/tests/jest/plugins/search/plugin.search.view.test.js +10 -18
- package/tests/jest/plugins/tts/WebTTSEngine.test.js +18 -12
- package/tests/jest/plugins/url/plugin.url.test.js +1 -1
- package/tests/jest/util/browserSniffing.test.js +9 -3
- package/tests/jest/utils.js +4 -1
@@ -1,13 +1,17 @@
|
|
1
|
+
// @ts-check
|
2
|
+
/** @typedef {import('@/src/BookReader.js').default} BookReader */
|
3
|
+
|
1
4
|
class SearchView {
|
2
5
|
/**
|
3
6
|
* @param {object} params
|
4
|
-
* @param {
|
5
|
-
* @param {function} params.
|
7
|
+
* @param {BookReader} params.br The BookReader instance
|
8
|
+
* @param {function} params.searchCancelledCallback callback when a user wants to cancel search
|
6
9
|
*
|
7
10
|
* @event BookReader:SearchResultsCleared - when the search results nav gets cleared
|
8
11
|
* @event BookReader:ToggleSearchMenu - when search results menu should toggle
|
9
12
|
*/
|
10
13
|
constructor({ br, searchCancelledCallback = () => {} }) {
|
14
|
+
/** @type {BookReader} */
|
11
15
|
this.br = br;
|
12
16
|
this.matches = [];
|
13
17
|
this.cacheDOMElements();
|
@@ -40,7 +44,7 @@ class SearchView {
|
|
40
44
|
}
|
41
45
|
|
42
46
|
clearSearchFieldAndResults(dispatchEventWhenComplete = true) {
|
43
|
-
this.br.removeSearchResults();
|
47
|
+
this.br._plugins.search.removeSearchResults();
|
44
48
|
this.removeResultPins();
|
45
49
|
this.emptyMatches();
|
46
50
|
this.setQuery('');
|
@@ -269,15 +273,15 @@ class SearchView {
|
|
269
273
|
$(event.target).addClass('front');
|
270
274
|
})
|
271
275
|
.on("mouseleave", (event) => $(event.target).removeClass('front'))
|
272
|
-
.on("click", () => { this.br.
|
276
|
+
.on("click", () => { this.br._plugins.search.jumpToMatch(match.matchIndex); });
|
273
277
|
});
|
274
278
|
}
|
275
279
|
|
276
280
|
/**
|
277
|
-
* @param {boolean}
|
281
|
+
* @param {boolean} show
|
278
282
|
*/
|
279
|
-
toggleSearchPending(
|
280
|
-
if (
|
283
|
+
toggleSearchPending(show = false) {
|
284
|
+
if (show) {
|
281
285
|
this.br.showProgressPopup("Search results will appear below...", () => this.progressPopupClosed());
|
282
286
|
}
|
283
287
|
else {
|
@@ -375,11 +379,11 @@ class SearchView {
|
|
375
379
|
|
376
380
|
handleSearchStarted() {
|
377
381
|
this.emptyMatches();
|
378
|
-
this.br.removeSearchHilites();
|
382
|
+
this.br._plugins.search.removeSearchHilites();
|
379
383
|
this.removeResultPins();
|
380
384
|
this.toggleSearchPending(true);
|
381
385
|
this.teardownSearchNavigation();
|
382
|
-
this.setQuery(this.br.searchTerm);
|
386
|
+
this.setQuery(this.br._plugins.search.searchTerm);
|
383
387
|
}
|
384
388
|
|
385
389
|
/**
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/* global br */
|
2
2
|
import { isChrome, isFirefox } from '../../util/browserSniffing.js';
|
3
|
-
import { isAndroid } from './utils.js';
|
3
|
+
import { isAndroid, DEBUG_READ_ALOUD } from './utils.js';
|
4
4
|
import { promisifyEvent, sleep } from '../../BookReader/utils.js';
|
5
5
|
import AbstractTTSEngine from './AbstractTTSEngine.js';
|
6
6
|
/** @typedef {import("./AbstractTTSEngine.js").PageChunk} PageChunk */
|
@@ -13,7 +13,7 @@ import AbstractTTSEngine from './AbstractTTSEngine.js';
|
|
13
13
|
**/
|
14
14
|
export default class WebTTSEngine extends AbstractTTSEngine {
|
15
15
|
static isSupported() {
|
16
|
-
return typeof(window.speechSynthesis) !== 'undefined'
|
16
|
+
return typeof(window.speechSynthesis) !== 'undefined';
|
17
17
|
}
|
18
18
|
|
19
19
|
/** @param {TTSEngineOptions} options */
|
@@ -48,6 +48,10 @@ export default class WebTTSEngine extends AbstractTTSEngine {
|
|
48
48
|
],
|
49
49
|
});
|
50
50
|
|
51
|
+
navigator.mediaSession.setPositionState({
|
52
|
+
duration: Infinity,
|
53
|
+
});
|
54
|
+
|
51
55
|
navigator.mediaSession.setActionHandler('play', () => {
|
52
56
|
audio.play();
|
53
57
|
this.resume();
|
@@ -147,12 +151,12 @@ export class WebTTSSound {
|
|
147
151
|
this.utterance.rate = this.rate;
|
148
152
|
|
149
153
|
// Useful for debugging things
|
150
|
-
if (
|
154
|
+
if (DEBUG_READ_ALOUD) {
|
151
155
|
this.utterance.addEventListener('pause', () => console.log('pause'));
|
152
156
|
this.utterance.addEventListener('resume', () => console.log('resume'));
|
153
157
|
this.utterance.addEventListener('start', () => console.log('start'));
|
154
158
|
this.utterance.addEventListener('end', () => console.log('end'));
|
155
|
-
this.utterance.addEventListener('error',
|
159
|
+
this.utterance.addEventListener('error', ev => console.log('error', ev));
|
156
160
|
this.utterance.addEventListener('boundary', () => console.log('boundary'));
|
157
161
|
this.utterance.addEventListener('mark', () => console.log('mark'));
|
158
162
|
this.utterance.addEventListener('finish', () => console.log('finish'));
|
@@ -260,24 +264,25 @@ export class WebTTSSound {
|
|
260
264
|
// 2. Pause doesn't work and doesn't fire
|
261
265
|
// 3. Pause works but doesn't fire
|
262
266
|
const pauseMightNotWork = (isFirefox() && isAndroid());
|
263
|
-
const pauseMightNotFire = isChrome() || pauseMightNotWork;
|
264
267
|
|
265
|
-
if
|
266
|
-
|
267
|
-
const timeoutPromise = sleep(100).then(() => 'timeout');
|
268
|
-
const result = await Promise.race([pausePromise, timeoutPromise]);
|
269
|
-
// We got our pause event; nothing to do!
|
270
|
-
if (result != 'timeout') return;
|
268
|
+
// Pause sometimes works, but doesn't fire the event, so wait to see if it fires
|
269
|
+
const winner = await Promise.race([pausePromise, sleep(100).then(() => 'timeout')]);
|
271
270
|
|
272
|
-
|
271
|
+
// We got our pause event; nothing to do!
|
272
|
+
if (winner != 'timeout') return;
|
273
273
|
|
274
|
-
|
275
|
-
|
274
|
+
if (DEBUG_READ_ALOUD) {
|
275
|
+
console.log('TTS: Firing pause event manually');
|
276
276
|
}
|
277
|
+
|
278
|
+
this.utterance.dispatchEvent(new CustomEvent('pause', this._lastEvents.start));
|
279
|
+
|
280
|
+
// if pause might not work, then we'll stop entirely and restart later
|
281
|
+
if (pauseMightNotWork) this.stop();
|
277
282
|
}
|
278
283
|
|
279
284
|
async resume() {
|
280
|
-
if (!this.started) {
|
285
|
+
if (!this.started || this.stopped) {
|
281
286
|
this.play();
|
282
287
|
return;
|
283
288
|
}
|
@@ -289,22 +294,24 @@ export class WebTTSSound {
|
|
289
294
|
// 2. Resume works + doesn't fire (Chrome Desktop)
|
290
295
|
// 3. Resume doesn't work + doesn't fire (Chrome/FF Android)
|
291
296
|
const resumeMightNotWork = (isChrome() && isAndroid()) || (isFirefox() && isAndroid());
|
292
|
-
const resumeMightNotFire = isChrome() || resumeMightNotWork;
|
293
297
|
|
294
298
|
// Try resume
|
295
299
|
const resumePromise = promisifyEvent(this.utterance, 'resume');
|
296
300
|
speechSynthesis.resume();
|
297
301
|
|
298
|
-
|
299
|
-
|
302
|
+
const winner = await Promise.race([resumePromise, sleep(100).then(() => 'timeout')]);
|
303
|
+
// We got resume! All is good
|
304
|
+
if (winner != 'timeout') return;
|
300
305
|
|
301
|
-
|
306
|
+
if (DEBUG_READ_ALOUD) {
|
307
|
+
console.log('TTS: Firing resume event manually');
|
308
|
+
}
|
302
309
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
310
|
+
// Fake it
|
311
|
+
this.utterance.dispatchEvent(new CustomEvent('resume', {}));
|
312
|
+
if (resumeMightNotWork) {
|
313
|
+
await this.reload();
|
314
|
+
this.play();
|
308
315
|
}
|
309
316
|
}
|
310
317
|
|
@@ -326,37 +333,56 @@ export class WebTTSSound {
|
|
326
333
|
* by pausing after 14 seconds and ~instantly resuming.
|
327
334
|
*/
|
328
335
|
async _chromePausingBugFix() {
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
336
|
+
if (DEBUG_READ_ALOUD) {
|
337
|
+
console.log('CHROME-PAUSE-HACK: starting');
|
338
|
+
}
|
339
|
+
|
340
|
+
const result = await Promise.race([
|
341
|
+
sleep(14000).then(() => 'timeout'),
|
342
|
+
promisifyEvent(this.utterance, 'pause').then(() => 'pause'),
|
343
|
+
promisifyEvent(this.utterance, 'end').then(() => 'end'),
|
344
|
+
// Some browsers (Edge) trigger error when the utterance is interrupted/stopped
|
345
|
+
promisifyEvent(this.utterance, 'error').then(() => 'error'),
|
346
|
+
]);
|
347
|
+
|
348
|
+
if (DEBUG_READ_ALOUD) {
|
334
349
|
console.log(`CHROME-PAUSE-HACK: ${result}`);
|
335
350
|
}
|
336
|
-
|
337
|
-
case 'ended':
|
351
|
+
if (result == 'end' || result == 'error') {
|
338
352
|
// audio was stopped/finished; nothing to do
|
339
|
-
|
340
|
-
|
353
|
+
if (DEBUG_READ_ALOUD) {
|
354
|
+
console.log('CHROME-PAUSE-HACK: stopped (end/error)');
|
355
|
+
}
|
356
|
+
} else if (result == 'pause') {
|
341
357
|
// audio was paused; wait for resume
|
342
358
|
// Chrome won't let you resume the audio if 14s have passed 🤷
|
343
359
|
// We could do the same as before (but resume+pause instead of pause+resume),
|
344
360
|
// but that means we'd _constantly_ be running in the background. So in that
|
345
361
|
// case, let's just restart the chunk
|
346
|
-
await Promise.race([
|
347
|
-
promisifyEvent(this.utterance, 'resume'),
|
362
|
+
const result2 = await Promise.race([
|
363
|
+
promisifyEvent(this.utterance, 'resume').then(() => 'resume'),
|
348
364
|
sleep(14000).then(() => 'timeout'),
|
349
365
|
]);
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
366
|
+
if (result2 == 'timeout') {
|
367
|
+
if (DEBUG_READ_ALOUD) {
|
368
|
+
console.log('CHROME-PAUSE-HACK: stopped (timed out while paused)');
|
369
|
+
}
|
370
|
+
// We hit Chrome's secret cut off time while paused, and
|
371
|
+
// won't be able to resume normally, so trigger a stop.
|
372
|
+
this.stop();
|
373
|
+
} else {
|
374
|
+
// The user resumed before the cut off! Continue as normal
|
375
|
+
this._chromePausingBugFix();
|
376
|
+
}
|
377
|
+
} else if (result == 'timeout') {
|
378
|
+
// We hit Chrome's secret cut off time while playing.
|
379
|
+
// To be able to keep TTS-ing, quickly pause/resume.
|
355
380
|
speechSynthesis.pause();
|
356
381
|
await sleep(25);
|
357
382
|
speechSynthesis.resume();
|
383
|
+
|
384
|
+
// Listen for more
|
358
385
|
this._chromePausingBugFix();
|
359
|
-
break;
|
360
386
|
}
|
361
387
|
}
|
362
388
|
}
|
@@ -278,10 +278,8 @@ export class TtsPlugin extends BookReaderPlugin {
|
|
278
278
|
* @param {Number} leafIndex
|
279
279
|
*/
|
280
280
|
async maybeFlipToIndex(leafIndex) {
|
281
|
-
if (this.br.
|
281
|
+
if (!this.br._isIndexDisplayed(leafIndex)) {
|
282
282
|
this.br.jumpToIndex(leafIndex);
|
283
|
-
} else {
|
284
|
-
await this.br._modes.mode2Up.mode2UpLit.jumpToIndex(leafIndex);
|
285
283
|
}
|
286
284
|
}
|
287
285
|
|
package/src/plugins/tts/utils.js
CHANGED
@@ -79,3 +79,16 @@ export function hasLocalStorage() {
|
|
79
79
|
return false;
|
80
80
|
}
|
81
81
|
}
|
82
|
+
|
83
|
+
export const DEBUG_READ_ALOUD = location.toString().indexOf('_debugReadAloud=true') != -1;
|
84
|
+
|
85
|
+
export async function checkIfFiresPause() {
|
86
|
+
// Pick some random text so that if it accidentally speaks, it's not too annoying
|
87
|
+
const u = new SpeechSynthesisUtterance("Loading");
|
88
|
+
let calledPause = false;
|
89
|
+
u.addEventListener('pause', () => calledPause = true);
|
90
|
+
speechSynthesis.speak(u);
|
91
|
+
await new Promise(res => setTimeout(res, 10));
|
92
|
+
speechSynthesis.pause();
|
93
|
+
return calledPause;
|
94
|
+
}
|
@@ -7,7 +7,17 @@
|
|
7
7
|
* @return {boolean}
|
8
8
|
*/
|
9
9
|
export function isChrome(userAgent = navigator.userAgent, vendor = navigator.vendor) {
|
10
|
-
return /chrome/i.test(userAgent) && /google inc/i.test(vendor);
|
10
|
+
return /chrome/i.test(userAgent) && /google inc/i.test(vendor) && !isEdge(userAgent);
|
11
|
+
}
|
12
|
+
|
13
|
+
/**
|
14
|
+
* Checks whether the current browser is a Edge browser
|
15
|
+
* See https://learn.microsoft.com/en-us/microsoft-edge/web-platform/user-agent-guidance
|
16
|
+
* @param {string} [userAgent]
|
17
|
+
* @return {boolean}
|
18
|
+
*/
|
19
|
+
export function isEdge(userAgent = navigator.userAgent) {
|
20
|
+
return /chrome/i.test(userAgent) && /\bEdg(e|A|iOS)?\b/i.test(userAgent);
|
11
21
|
}
|
12
22
|
|
13
23
|
/**
|
@@ -2,7 +2,7 @@ export const TEST_TEXT_FOUND = 'theory';
|
|
2
2
|
export const TEST_TEXT_NOT_FOUND = 'HopefullyNotFoundLongWord';
|
3
3
|
export const PAGE_FIRST_RESULT = 30;
|
4
4
|
|
5
|
-
export const SEARCH_INSIDE_URL_RE =
|
5
|
+
export const SEARCH_INSIDE_URL_RE = /\/fulltext\/inside\.php/;
|
6
6
|
|
7
7
|
/** Mock response for a matching search term. */
|
8
8
|
export function mockResponseFound(req, res) {
|
@@ -164,14 +164,20 @@ describe('<book-navigator>', () => {
|
|
164
164
|
expect(el.menuProviders.visualAdjustments).toBeInstanceOf(VisualAdjustmentsProvider);
|
165
165
|
});
|
166
166
|
describe('Loading Sub Menus By Plugin Flags', () => {
|
167
|
-
test('Search: uses `
|
167
|
+
test('Search: uses `enabled` flag', async() => {
|
168
168
|
const el = fixtureSync(container());
|
169
169
|
const $brContainer = document.createElement('div');
|
170
170
|
const brStub = {
|
171
171
|
resize: sinon.fake(),
|
172
172
|
currentIndex: sinon.fake(),
|
173
173
|
jumpToIndex: sinon.fake(),
|
174
|
-
options: {
|
174
|
+
options: {
|
175
|
+
plugins: {
|
176
|
+
search: {
|
177
|
+
enabled: true,
|
178
|
+
},
|
179
|
+
},
|
180
|
+
},
|
175
181
|
refs: {
|
176
182
|
$brContainer,
|
177
183
|
},
|
@@ -346,7 +352,6 @@ describe('<book-navigator>', () => {
|
|
346
352
|
|
347
353
|
let sidePanelConfig = {};
|
348
354
|
el.addEventListener('updateSideMenu', (e) => {
|
349
|
-
console.log();
|
350
355
|
sidePanelConfig = e.detail;
|
351
356
|
});
|
352
357
|
const toggleSearchMenuEvent = new Event('BookReader:ToggleSearchMenu');
|
@@ -88,7 +88,11 @@ describe('Search Provider', () => {
|
|
88
88
|
onProviderChange: sinon.fake(),
|
89
89
|
bookreader: {
|
90
90
|
leafNumToIndex: sinon.fake(),
|
91
|
-
|
91
|
+
_plugins: {
|
92
|
+
search: {
|
93
|
+
jumpToMatch: sinon.fake(),
|
94
|
+
},
|
95
|
+
},
|
92
96
|
},
|
93
97
|
});
|
94
98
|
|
@@ -100,7 +104,7 @@ describe('Search Provider', () => {
|
|
100
104
|
{ detail: searchResultStub }),
|
101
105
|
);
|
102
106
|
|
103
|
-
expect(provider.bookreader.
|
107
|
+
expect(provider.bookreader._plugins.search.jumpToMatch.callCount).toEqual(1);
|
104
108
|
});
|
105
109
|
test('update url when search is cancelled or input cleared', async() => {
|
106
110
|
const urlPluginMock = {
|
@@ -111,7 +115,11 @@ describe('Search Provider', () => {
|
|
111
115
|
onProviderChange: sinon.fake(),
|
112
116
|
bookreader: {
|
113
117
|
leafNumToIndex: sinon.fake(),
|
114
|
-
|
118
|
+
_plugins: {
|
119
|
+
search: {
|
120
|
+
jumpToMatch: sinon.fake(),
|
121
|
+
},
|
122
|
+
},
|
115
123
|
urlPlugin: urlPluginMock,
|
116
124
|
},
|
117
125
|
});
|
@@ -145,7 +153,11 @@ describe('Search Provider', () => {
|
|
145
153
|
onProviderChange: sinon.fake(),
|
146
154
|
bookreader: {
|
147
155
|
leafNumToIndex: sinon.fake(),
|
148
|
-
|
156
|
+
_plugins: {
|
157
|
+
search: {
|
158
|
+
jumpToMatch: sinon.fake(),
|
159
|
+
},
|
160
|
+
},
|
149
161
|
urlPlugin: urlPluginMock,
|
150
162
|
search: sinon.fake(),
|
151
163
|
},
|
@@ -191,3 +191,73 @@ describe('`BookReader.prototype.prev`', () => {
|
|
191
191
|
});
|
192
192
|
});
|
193
193
|
|
194
|
+
describe('`BookReader.prototype.jumpToIndex`', () => {
|
195
|
+
/**
|
196
|
+
* @param {Partial<BookReaderOptions>} overrides
|
197
|
+
*/
|
198
|
+
function makeFakeBr(overrides = {}) {
|
199
|
+
const br = new BookReader({
|
200
|
+
data: [
|
201
|
+
[
|
202
|
+
{ index: 0, viewable: true },
|
203
|
+
],
|
204
|
+
[
|
205
|
+
{ index: 1, viewable: false },
|
206
|
+
{ index: 2, viewable: false },
|
207
|
+
],
|
208
|
+
[
|
209
|
+
{ index: 3, viewable: false },
|
210
|
+
{ index: 4, viewable: false },
|
211
|
+
],
|
212
|
+
[
|
213
|
+
{ index: 5, viewable: true },
|
214
|
+
],
|
215
|
+
],
|
216
|
+
...overrides,
|
217
|
+
});
|
218
|
+
br.init();
|
219
|
+
|
220
|
+
br._modes.mode2Up.jumpToIndex = sinon.fake();
|
221
|
+
|
222
|
+
expect(br.firstIndex).toBe(0);
|
223
|
+
expect(br.mode).toBe(br.constMode2up);
|
224
|
+
|
225
|
+
return br;
|
226
|
+
}
|
227
|
+
|
228
|
+
test('Jumping into an unviewables range will go to start of range', () => {
|
229
|
+
const br = makeFakeBr();
|
230
|
+
br.jumpToIndex(3, 0, 0, true);
|
231
|
+
expect(br._modes.mode2Up.jumpToIndex.callCount).toBe(1);
|
232
|
+
expect(br._modes.mode2Up.jumpToIndex.args[0][0]).toBe(1);
|
233
|
+
});
|
234
|
+
|
235
|
+
test('Trying to jump into unviewables range while in that range, will jump forward', () => {
|
236
|
+
const br = makeFakeBr();
|
237
|
+
br.displayedIndices = [1, 2];
|
238
|
+
br.jumpToIndex(3, 0, 0, true);
|
239
|
+
expect(br._modes.mode2Up.jumpToIndex.callCount).toBe(1);
|
240
|
+
expect(br._modes.mode2Up.jumpToIndex.args[0][0]).toBe(5);
|
241
|
+
});
|
242
|
+
|
243
|
+
test('Trying to jump into unviewables range while in that range, will do nothing if cannot jump forward', () => {
|
244
|
+
const br = makeFakeBr({
|
245
|
+
data: [
|
246
|
+
[
|
247
|
+
{ index: 0, viewable: true },
|
248
|
+
],
|
249
|
+
[
|
250
|
+
{ index: 1, viewable: false },
|
251
|
+
{ index: 2, viewable: false },
|
252
|
+
],
|
253
|
+
[
|
254
|
+
{ index: 3, viewable: false },
|
255
|
+
{ index: 4, viewable: false },
|
256
|
+
],
|
257
|
+
],
|
258
|
+
});
|
259
|
+
br.displayedIndices = [1, 2];
|
260
|
+
br.jumpToIndex(3, 0, 0, true);
|
261
|
+
expect(br._modes.mode2Up.jumpToIndex.callCount).toBe(0);
|
262
|
+
});
|
263
|
+
});
|
@@ -7,7 +7,12 @@ import '@/src/plugins/url/plugin.url.js';
|
|
7
7
|
let br;
|
8
8
|
beforeAll(() => {
|
9
9
|
document.body.innerHTML = '<div id="BookReader">';
|
10
|
-
br = new BookReader(
|
10
|
+
br = new BookReader({
|
11
|
+
server: '',
|
12
|
+
bookId: '',
|
13
|
+
subPrefix: '',
|
14
|
+
bookPath: '',
|
15
|
+
});
|
11
16
|
});
|
12
17
|
|
13
18
|
afterEach(() => {
|
@@ -91,6 +96,26 @@ test('calls switchMode with init option when init called', () => {
|
|
91
96
|
.toHaveProperty('init', true);
|
92
97
|
});
|
93
98
|
|
99
|
+
test('has added BR property: server', () => {
|
100
|
+
expect(br).toHaveProperty('server');
|
101
|
+
expect(br.server).toBeDefined();
|
102
|
+
});
|
103
|
+
|
104
|
+
test('has added BR property: bookId', () => {
|
105
|
+
expect(br).toHaveProperty('bookId');
|
106
|
+
expect(br.bookId).toBeDefined();
|
107
|
+
});
|
108
|
+
|
109
|
+
test('has added BR property: subPrefix', () => {
|
110
|
+
expect(br).toHaveProperty('subPrefix');
|
111
|
+
expect(br.subPrefix).toBeDefined();
|
112
|
+
});
|
113
|
+
|
114
|
+
test('has added BR property: bookPath', () => {
|
115
|
+
expect(br).toHaveProperty('bookPath');
|
116
|
+
expect(br.bookPath).toBeDefined();
|
117
|
+
});
|
118
|
+
|
94
119
|
test('has suppressFragmentChange true when init with no input', () => {
|
95
120
|
br._plugins.resume.getResumeValue = jest.fn(() => null);
|
96
121
|
br.urlReadFragment = jest.fn(() => '');
|