@internetarchive/bookreader 5.0.0-40 → 5.0.0-40-a1
Sign up to get free protection for your applications and to get access to all the features.
- package/BookReader/BookReader.css +6 -39
- package/BookReader/BookReader.js +1 -1
- package/BookReader/BookReader.js.map +1 -1
- package/BookReader/ia-bookreader-bundle.js +185 -55
- package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +25 -0
- package/BookReader/ia-bookreader-bundle.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.tts.js +1 -1
- package/BookReader/plugins/plugin.tts.js.map +1 -1
- package/CHANGELOG.md +0 -5
- package/package.json +2 -2
- package/src/BookNavigator/search/search-provider.js +8 -6
- package/src/BookReader/PageContainer.js +2 -14
- package/src/BookReader/utils.js +0 -24
- package/src/BookReader.js +4 -2
- package/src/css/_BRsearch.scss +5 -18
- package/src/plugins/search/plugin.search.js +14 -60
- package/src/plugins/search/view.js +7 -1
- package/src/plugins/tts/FestivalTTSEngine.js +1 -1
- package/src/plugins/tts/WebTTSEngine.js +1 -2
- package/src/plugins/tts/utils.js +9 -0
- package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +1 -1
- package/tests/jest/BookReader/PageContainer.test.js +0 -9
- package/tests/jest/BookReader/utils.test.js +0 -50
- package/tests/jest/plugins/tts/utils.test.js +25 -0
- package/tests/karma/BookNavigator/search/search-provider.test.js +17 -0
package/CHANGELOG.md
CHANGED
@@ -1,8 +1,3 @@
|
|
1
|
-
# 5.0.0-40
|
2
|
-
Fix: Better search highlights @cdrini
|
3
|
-
Dev: update lit 2 components @iisa
|
4
|
-
Dev: update lit @renovate
|
5
|
-
|
6
1
|
# 5.0.0-39
|
7
2
|
Fix: Performance improvements to scroll/zooming when text layer is larger @cdrini
|
8
3
|
Fix: Update zoom in/out icons to match iconochive glyphs @pezvi
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@internetarchive/bookreader",
|
3
|
-
"version": "5.0.0-40",
|
3
|
+
"version": "5.0.0-40-a1",
|
4
4
|
"description": "The Internet Archive BookReader.",
|
5
5
|
"repository": {
|
6
6
|
"type": "git",
|
@@ -38,7 +38,7 @@
|
|
38
38
|
"@internetarchive/icon-visual-adjustment": "^1.3.2",
|
39
39
|
"@internetarchive/modal-manager": "^0.2.1",
|
40
40
|
"@internetarchive/shared-resize-observer": "^0.2.0",
|
41
|
-
"lit": "^2.
|
41
|
+
"lit": "^2.1.3"
|
42
42
|
},
|
43
43
|
"devDependencies": {
|
44
44
|
"@babel/core": "7.17.5",
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import { html, nothing } from 'lit';
|
2
2
|
import '@internetarchive/icon-search/icon-search';
|
3
3
|
import './search-results';
|
4
|
-
/** @typedef {import('@/src/plugins/search/plugin.search.js').SearchInsideMatch} SearchInsideMatch */
|
5
4
|
|
6
5
|
let searchState = {
|
7
6
|
query: '',
|
@@ -29,10 +28,10 @@ export default class SearchProvider {
|
|
29
28
|
this.bindEventListeners = this.bindEventListeners.bind(this);
|
30
29
|
this.getMenuDetails = this.getMenuDetails.bind(this);
|
31
30
|
this.getComponent = this.getComponent.bind(this);
|
31
|
+
this.advanceToPage = this.advanceToPage.bind(this);
|
32
32
|
this.updateMenu = this.updateMenu.bind(this);
|
33
33
|
|
34
34
|
this.onProviderChange = onProviderChange;
|
35
|
-
/** @type {import('@/src/BookReader.js').default} */
|
36
35
|
this.bookreader = bookreader;
|
37
36
|
this.icon = html`<ia-icon-search style="width: var(--iconWidth); height: var(--iconHeight);"></ia-icon-search>`;
|
38
37
|
this.label = 'Search inside';
|
@@ -175,10 +174,13 @@ export default class SearchProvider {
|
|
175
174
|
`;
|
176
175
|
}
|
177
176
|
|
178
|
-
/**
|
179
|
-
* @param {{ detail: {match: SearchInsideMatch} }} param0
|
180
|
-
*/
|
181
177
|
onSearchResultsClicked({ detail }) {
|
182
|
-
|
178
|
+
const page = detail.match.par[0].page;
|
179
|
+
this.advanceToPage(page);
|
180
|
+
}
|
181
|
+
|
182
|
+
advanceToPage(leaf) {
|
183
|
+
const page = this.bookreader.leafNumToIndex(leaf);
|
184
|
+
this.bookreader._searchPluginGoToResult(page);
|
183
185
|
}
|
184
186
|
}
|
@@ -103,10 +103,6 @@ export function boxToSVGRect({ l: left, r: right, b: bottom, t: top }) {
|
|
103
103
|
rect.setAttribute("y", top.toString());
|
104
104
|
rect.setAttribute("width", (right - left).toString());
|
105
105
|
rect.setAttribute("height", (bottom - top).toString());
|
106
|
-
|
107
|
-
// Some style; corner radius 4px. Can't set this in CSS yet
|
108
|
-
rect.setAttribute("rx", "4");
|
109
|
-
rect.setAttribute("ry", "4");
|
110
106
|
return rect;
|
111
107
|
}
|
112
108
|
|
@@ -115,9 +111,8 @@ export function boxToSVGRect({ l: left, r: right, b: bottom, t: top }) {
|
|
115
111
|
* @param {Array<{ l: number, r: number, b: number, t: number }>} boxes
|
116
112
|
* @param {PageModel} page
|
117
113
|
* @param {HTMLElement} containerEl
|
118
|
-
* @param {string[]} [rectClasses] CSS classes to add to the rects
|
119
114
|
*/
|
120
|
-
export function renderBoxesInPageContainerLayer(layerClass, boxes, page, containerEl
|
115
|
+
export function renderBoxesInPageContainerLayer(layerClass, boxes, page, containerEl) {
|
121
116
|
const mountedSvg = containerEl.querySelector(`.${layerClass}`);
|
122
117
|
// Create the layer if it's not there
|
123
118
|
const svg = mountedSvg || createSVGPageLayer(page, layerClass);
|
@@ -127,12 +122,5 @@ export function renderBoxesInPageContainerLayer(layerClass, boxes, page, contain
|
|
127
122
|
if (imgEl) $(svg).insertAfter(imgEl);
|
128
123
|
else $(svg).prependTo(containerEl);
|
129
124
|
}
|
130
|
-
|
131
|
-
for (const [i, box] of boxes.entries()) {
|
132
|
-
const rect = boxToSVGRect(box);
|
133
|
-
if (rectClasses) {
|
134
|
-
rect.setAttribute('class', rectClasses[i]);
|
135
|
-
}
|
136
|
-
svg.appendChild(rect);
|
137
|
-
}
|
125
|
+
boxes.forEach(box => svg.appendChild(boxToSVGRect(box)));
|
138
126
|
}
|
package/src/BookReader/utils.js
CHANGED
@@ -238,27 +238,3 @@ export function arrEquals(arr1, arr2) {
|
|
238
238
|
export function arrChanged(arr1, arr2) {
|
239
239
|
return arr1 && arr2 && !arrEquals(arr1, arr2);
|
240
240
|
}
|
241
|
-
|
242
|
-
/**
|
243
|
-
* Waits the provided number of ms and then resolves with a promise
|
244
|
-
* @param {number} ms
|
245
|
-
**/
|
246
|
-
export async function sleep(ms) {
|
247
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
248
|
-
}
|
249
|
-
|
250
|
-
/**
|
251
|
-
* @template T
|
252
|
-
* @param {function(): T} fn
|
253
|
-
* @param {Object} options
|
254
|
-
* @param {function(T): boolean} [options.until]
|
255
|
-
* @return {T | undefined}
|
256
|
-
*/
|
257
|
-
export async function poll(fn, { step = 50, timeout = 500, until = val => Boolean(val), _sleep = sleep } = {}) {
|
258
|
-
const startTime = Date.now();
|
259
|
-
while (Date.now() - startTime < timeout) {
|
260
|
-
const result = fn();
|
261
|
-
if (until(result)) return result;
|
262
|
-
await _sleep(step);
|
263
|
-
}
|
264
|
-
}
|
package/src/BookReader.js
CHANGED
@@ -1006,8 +1006,10 @@ BookReader.prototype.jumpToPage = function(pageNum) {
|
|
1006
1006
|
* @param {PageIndex} index
|
1007
1007
|
*/
|
1008
1008
|
BookReader.prototype._isIndexDisplayed = function(index) {
|
1009
|
-
|
1010
|
-
|
1009
|
+
// One up "caches" pages +- current, so exclude those in the test.
|
1010
|
+
return this.constMode1up == this.mode ? this.displayedIndices.slice(1, -1).includes(index) :
|
1011
|
+
this.displayedIndices ? this.displayedIndices.includes(index) :
|
1012
|
+
this.currentIndex() == index;
|
1011
1013
|
};
|
1012
1014
|
|
1013
1015
|
/**
|
package/src/css/_BRsearch.scss
CHANGED
@@ -31,22 +31,9 @@
|
|
31
31
|
pointer-events: none;
|
32
32
|
|
33
33
|
rect {
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
}
|
38
|
-
}
|
39
|
-
|
40
|
-
.searchHiliteLayer rect {
|
41
|
-
animation: highlightFocus 600ms 1 reverse;
|
42
|
-
stroke: blue;
|
43
|
-
stroke-width: 4px;
|
44
|
-
|
45
|
-
// Sass for loop for nth-child animation delay
|
46
|
-
@for $i from 1 through 10 {
|
47
|
-
&:nth-child(#{$i}) {
|
48
|
-
animation-delay: #{($i - 1) * 50}ms;
|
49
|
-
}
|
34
|
+
fill: #0000ff;
|
35
|
+
fill-opacity: 0.2;
|
36
|
+
animation: hiliteFadeIn .2s;
|
50
37
|
}
|
51
38
|
}
|
52
39
|
|
@@ -220,8 +207,8 @@
|
|
220
207
|
}
|
221
208
|
}
|
222
209
|
|
223
|
-
@keyframes
|
224
|
-
|
210
|
+
@keyframes hiliteFadeIn {
|
211
|
+
from { fill-opacity: 0; }
|
225
212
|
}
|
226
213
|
|
227
214
|
/* Mid size breakpoint */
|
@@ -1,4 +1,3 @@
|
|
1
|
-
// @ts-check
|
2
1
|
/* global BookReader */
|
3
2
|
/**
|
4
3
|
* Plugin for Archive.org book search
|
@@ -24,7 +23,6 @@
|
|
24
23
|
* @event BookReader:SearchCanceled - When no results found. Receives
|
25
24
|
* `instance`
|
26
25
|
*/
|
27
|
-
import { poll } from '../../BookReader/utils.js';
|
28
26
|
import { renderBoxesInPageContainerLayer } from '../../BookReader/PageContainer.js';
|
29
27
|
import SearchView from './view.js';
|
30
28
|
/** @typedef {import('../../BookReader/PageContainer').PageContainer} PageContainer */
|
@@ -114,14 +112,7 @@ BookReader.prototype._createPageContainer = (function (super_) {
|
|
114
112
|
const pageContainer = super_.call(this, index);
|
115
113
|
if (this.enableSearch && pageContainer.page && index in this._searchBoxesByIndex) {
|
116
114
|
const pageIndex = pageContainer.page.index;
|
117
|
-
|
118
|
-
renderBoxesInPageContainerLayer(
|
119
|
-
'searchHiliteLayer',
|
120
|
-
boxes,
|
121
|
-
pageContainer.page,
|
122
|
-
pageContainer.$container[0],
|
123
|
-
boxes.map(b => `match-index-${b.matchIndex}`),
|
124
|
-
);
|
115
|
+
renderBoxesInPageContainerLayer('searchHiliteLayer', this._searchBoxesByIndex[pageIndex], pageContainer.page, pageContainer.$container[0]);
|
125
116
|
}
|
126
117
|
return pageContainer;
|
127
118
|
};
|
@@ -189,7 +180,7 @@ BookReader.prototype.search = async function(term = '', overrides = {}) {
|
|
189
180
|
|
190
181
|
const url = `${baseUrl}${paramStr}`;
|
191
182
|
|
192
|
-
const
|
183
|
+
const processSearchResults = (searchInsideResults) => {
|
193
184
|
if (this.searchCancelled) {
|
194
185
|
return;
|
195
186
|
}
|
@@ -209,7 +200,7 @@ BookReader.prototype.search = async function(term = '', overrides = {}) {
|
|
209
200
|
};
|
210
201
|
|
211
202
|
this.trigger('SearchStarted', { term: this.searchTerm, instance: this });
|
212
|
-
|
203
|
+
return processSearchResults(await $.ajax({
|
213
204
|
url: url,
|
214
205
|
dataType: 'jsonp',
|
215
206
|
cache: true,
|
@@ -251,12 +242,10 @@ BookReader.prototype.cancelSearchRequest = function () {
|
|
251
242
|
* @property {number} b
|
252
243
|
* @property {number} t
|
253
244
|
* @property {HTMLDivElement} [div]
|
254
|
-
* @property {number} matchIndex This is a fake field! not part of the API response. The index of the match that contains this box in total search results matches.
|
255
245
|
*/
|
256
246
|
|
257
247
|
/**
|
258
248
|
* @typedef {object} SearchInsideMatch
|
259
|
-
* @property {number} matchIndex This is a fake field! Not part of the API response. It is added by the JS.
|
260
249
|
* @property {string} text
|
261
250
|
* @property {Array<{ page: number, boxes: SearchInsideMatchBox[] }>} par
|
262
251
|
*/
|
@@ -270,27 +259,19 @@ BookReader.prototype.cancelSearchRequest = function () {
|
|
270
259
|
|
271
260
|
/**
|
272
261
|
* Search Results return handler
|
262
|
+
* @callback
|
273
263
|
* @param {SearchInsideResults} results
|
274
264
|
* @param {object} options
|
275
265
|
* @param {boolean} options.goToFirstResult
|
276
266
|
*/
|
277
267
|
BookReader.prototype.BRSearchCallback = function(results, options) {
|
278
|
-
// Attach matchIndex to a few things to make it easier to identify
|
279
|
-
// an active/selected match
|
280
|
-
for (const [index, match] of results.matches.entries()) {
|
281
|
-
match.matchIndex = index;
|
282
|
-
for (const par of match.par) {
|
283
|
-
for (const box of par.boxes) {
|
284
|
-
box.matchIndex = index;
|
285
|
-
}
|
286
|
-
}
|
287
|
-
}
|
288
268
|
this.searchResults = results || [];
|
289
269
|
|
290
270
|
this.updateSearchHilites();
|
291
271
|
this.removeProgressPopup();
|
292
272
|
if (options.goToFirstResult) {
|
293
|
-
this.
|
273
|
+
const pageIndex = this._models.book.leafNumToIndex(results.matches[0].par[0].page);
|
274
|
+
this._searchPluginGoToResult(pageIndex);
|
294
275
|
}
|
295
276
|
this.trigger('SearchCallback', { results, options, instance: this });
|
296
277
|
};
|
@@ -335,7 +316,7 @@ BookReader.prototype._BRSearchCallbackError = function(results) {
|
|
335
316
|
BookReader.prototype.updateSearchHilites = function() {
|
336
317
|
/** @type {SearchInsideMatch[]} */
|
337
318
|
const matches = this.searchResults?.matches || [];
|
338
|
-
/** @type { {[pageIndex: number]:
|
319
|
+
/** @type { {[pageIndex: number]: SearchInsideMatch[]} } */
|
339
320
|
const boxesByIndex = {};
|
340
321
|
|
341
322
|
// Clear any existing svg layers
|
@@ -345,8 +326,8 @@ BookReader.prototype.updateSearchHilites = function() {
|
|
345
326
|
for (const match of matches) {
|
346
327
|
for (const box of match.par[0].boxes) {
|
347
328
|
const pageIndex = this.leafNumToIndex(box.page);
|
348
|
-
const
|
349
|
-
|
329
|
+
const pageMatches = boxesByIndex[pageIndex] || (boxesByIndex[pageIndex] = []);
|
330
|
+
pageMatches.push(box);
|
350
331
|
}
|
351
332
|
}
|
352
333
|
|
@@ -355,9 +336,7 @@ BookReader.prototype.updateSearchHilites = function() {
|
|
355
336
|
const pageIndex = parseFloat(pageIndexString);
|
356
337
|
const page = this._models.book.getPage(pageIndex);
|
357
338
|
const pageContainers = this.getActivePageContainerElementsForIndex(pageIndex);
|
358
|
-
|
359
|
-
renderBoxesInPageContainerLayer('searchHiliteLayer', boxes, page, container, boxes.map(b => `match-index-${b.matchIndex}`));
|
360
|
-
}
|
339
|
+
pageContainers.forEach(container => renderBoxesInPageContainerLayer('searchHiliteLayer', boxes, page, container));
|
361
340
|
}
|
362
341
|
|
363
342
|
this._searchBoxesByIndex = boxesByIndex;
|
@@ -375,14 +354,11 @@ BookReader.prototype.removeSearchHilites = function() {
|
|
375
354
|
* Goes to the page specified. If the page is not viewable, tries to load the page
|
376
355
|
* FIXME Most of this logic is IA specific, and should be less integrated into here
|
377
356
|
* or at least more configurable.
|
378
|
-
* @param {
|
357
|
+
* @param {PageIndex} pageIndex
|
379
358
|
*/
|
380
|
-
BookReader.prototype._searchPluginGoToResult = async function (
|
381
|
-
const match = this.searchResults?.matches[matchIndex];
|
382
|
-
const pageIndex = this.leafNumToIndex(match.par[0].page);
|
359
|
+
BookReader.prototype._searchPluginGoToResult = async function (pageIndex) {
|
383
360
|
const { book } = this._models;
|
384
361
|
const page = book.getPage(pageIndex);
|
385
|
-
const onNearbyPage = Math.abs(this.currentIndex() - pageIndex) < 3;
|
386
362
|
let makeUnviewableAtEnd = false;
|
387
363
|
if (!page.isViewable) {
|
388
364
|
const resp = await fetch('/services/bookreader/request_page?' + new URLSearchParams({
|
@@ -403,35 +379,13 @@ BookReader.prototype._searchPluginGoToResult = async function (matchIndex) {
|
|
403
379
|
}
|
404
380
|
}
|
405
381
|
/* this updates the URL */
|
406
|
-
|
407
|
-
|
408
|
-
this.jumpToIndex(pageIndex);
|
409
|
-
}
|
382
|
+
this.suppressFragmentChange = false;
|
383
|
+
this.jumpToIndex(pageIndex);
|
410
384
|
|
411
385
|
// Reset it to unviewable if it wasn't resolved
|
412
386
|
if (makeUnviewableAtEnd) {
|
413
387
|
book.getPage(pageIndex).makeViewable(false);
|
414
388
|
}
|
415
|
-
|
416
|
-
// Scroll/flash in the ui
|
417
|
-
const $boxes = await poll(() => $(`rect.match-index-${match.matchIndex}`), { until: result => result.length > 0 });
|
418
|
-
if ($boxes.length) {
|
419
|
-
$boxes.css('animation', 'none');
|
420
|
-
$boxes[0].scrollIntoView({
|
421
|
-
// Only vertically center the highlight if we're in 1up or in full screen. In
|
422
|
-
// 2up, if we're not fullscreen, the whole body gets scrolled around to try to
|
423
|
-
// center the highlight 🙄 See:
|
424
|
-
// https://stackoverflow.com/questions/11039885/scrollintoview-causing-the-whole-page-to-move/11041376
|
425
|
-
// Note: nearest doesn't quite work great, because the ReadAloud toolbar is now
|
426
|
-
// full-width, and covers up the last line of the highlight.
|
427
|
-
block: this.constMode1up == this.mode || this.isFullscreenActive ? 'center' : 'nearest',
|
428
|
-
inline: 'center',
|
429
|
-
behavior: onNearbyPage ? 'smooth' : 'auto',
|
430
|
-
});
|
431
|
-
// wait for animation to start
|
432
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
433
|
-
$boxes.removeAttr("style");
|
434
|
-
}
|
435
389
|
};
|
436
390
|
|
437
391
|
/**
|
@@ -260,6 +260,7 @@ class SearchView {
|
|
260
260
|
<div>${uiStringPage} ${pageNumber}</div>
|
261
261
|
</div>
|
262
262
|
`)
|
263
|
+
.data({ pageIndex })
|
263
264
|
.appendTo(this.br.$('.BRnavline'))
|
264
265
|
.on("mouseenter", (event) => {
|
265
266
|
// remove from other markers then turn on just for this
|
@@ -276,7 +277,12 @@ class SearchView {
|
|
276
277
|
$(event.target).addClass('front');
|
277
278
|
})
|
278
279
|
.on("mouseleave", (event) => $(event.target).removeClass('front'))
|
279
|
-
.on("click", ()
|
280
|
+
.on("click", function (event) {
|
281
|
+
// closures are nested and deep, using an arrow function breaks references.
|
282
|
+
// Todo: update to arrow function & clean up closures
|
283
|
+
// to remove `bind` dependency
|
284
|
+
this.br._searchPluginGoToResult(+$(event.target).data('pageIndex'));
|
285
|
+
}.bind(this));
|
280
286
|
});
|
281
287
|
}
|
282
288
|
|
@@ -1,7 +1,6 @@
|
|
1
1
|
/* global br */
|
2
2
|
import { isChrome, isFirefox } from '../../util/browserSniffing.js';
|
3
|
-
import { promisifyEvent, isAndroid } from './utils.js';
|
4
|
-
import { sleep } from '../../BookReader/utils.js';
|
3
|
+
import { sleep, promisifyEvent, isAndroid } from './utils.js';
|
5
4
|
import AbstractTTSEngine from './AbstractTTSEngine.js';
|
6
5
|
/** @typedef {import("./AbstractTTSEngine.js").PageChunk} PageChunk */
|
7
6
|
/** @typedef {import("./AbstractTTSEngine.js").AbstractTTSSound} AbstractTTSSound */
|
package/src/plugins/tts/utils.js
CHANGED
@@ -26,6 +26,15 @@ export function approximateWordCount(text) {
|
|
26
26
|
return m ? m.length : 0;
|
27
27
|
}
|
28
28
|
|
29
|
+
/**
|
30
|
+
* Waits the provided number of ms and then resolves with a promise
|
31
|
+
* @param {number} ms
|
32
|
+
* @return {Promise}
|
33
|
+
*/
|
34
|
+
export function sleep(ms) {
|
35
|
+
return new Promise(res => setTimeout(res, ms));
|
36
|
+
}
|
37
|
+
|
29
38
|
/**
|
30
39
|
* Checks whether the current browser is on android
|
31
40
|
* @param {string} [userAgent]
|
@@ -178,15 +178,6 @@ describe('renderBoxesInPageContainerLayer', () => {
|
|
178
178
|
expect(container.querySelectorAll('.foo rect').length).toBe(3);
|
179
179
|
});
|
180
180
|
|
181
|
-
test('Adds optional classes', () => {
|
182
|
-
const container = document.createElement('div');
|
183
|
-
const page = { width: 100, height: 200 };
|
184
|
-
const boxes = [{l: 1, r: 2, t: 3, b: 4}, {l: 1, r: 2, t: 3, b: 4}, {l: 1, r: 2, t: 3, b: 4}];
|
185
|
-
renderBoxesInPageContainerLayer('foo', boxes, page, container, ['match-1', 'match-2', 'match-3']);
|
186
|
-
const rects = Array.from(container.querySelectorAll('.foo rect'));
|
187
|
-
expect(rects.map(r => r.getAttribute('class'))).toEqual(['match-1', 'match-2', 'match-3']);
|
188
|
-
});
|
189
|
-
|
190
181
|
test('Handles no boxes', () => {
|
191
182
|
const container = document.createElement('div');
|
192
183
|
const page = { width: 100, height: 200 };
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import sinon from 'sinon';
|
2
|
-
import { afterEventLoop } from '../utils.js';
|
3
2
|
import {
|
4
3
|
clamp,
|
5
4
|
cssPercentage,
|
@@ -9,10 +8,8 @@ import {
|
|
9
8
|
escapeHTML,
|
10
9
|
getActiveElement,
|
11
10
|
isInputActive,
|
12
|
-
poll,
|
13
11
|
polyfillCustomEvent,
|
14
12
|
PolyfilledCustomEvent,
|
15
|
-
sleep,
|
16
13
|
} from '@/src/BookReader/utils.js';
|
17
14
|
|
18
15
|
test('clamp function returns Math.min(Math.max(value, min), max)', () => {
|
@@ -137,50 +134,3 @@ describe('PolyfilledCustomEvent', () => {
|
|
137
134
|
expect(initCustomEventSpy.callCount).toBe(1);
|
138
135
|
});
|
139
136
|
});
|
140
|
-
|
141
|
-
describe('poll', () => {
|
142
|
-
beforeEach(() => jest.useFakeTimers());
|
143
|
-
afterEach(() => jest.useRealTimers());
|
144
|
-
test('polls until condition is true', async () => {
|
145
|
-
const fakeSleep = sinon.spy((ms) => jest.advanceTimersByTime(ms));
|
146
|
-
|
147
|
-
const returns = [null, null, 'foo'];
|
148
|
-
const check = sinon.spy(() => returns.shift());
|
149
|
-
const result = await poll(check, {_sleep: fakeSleep});
|
150
|
-
expect(fakeSleep.callCount).toBe(2);
|
151
|
-
expect(result).toBe('foo');
|
152
|
-
expect(check.callCount).toBe(3);
|
153
|
-
});
|
154
|
-
|
155
|
-
test('times out eventually', async () => {
|
156
|
-
const fakeSleep = sinon.spy((ms) => jest.advanceTimersByTime(ms));
|
157
|
-
|
158
|
-
const check = sinon.stub().returns(null);
|
159
|
-
const result = await poll(check, {_sleep: fakeSleep});
|
160
|
-
expect(result).toBeUndefined();
|
161
|
-
expect(check.callCount).toBe(10);
|
162
|
-
});
|
163
|
-
});
|
164
|
-
|
165
|
-
describe('sleep', () => {
|
166
|
-
test('Sleep 0 doest not called immediately', async () => {
|
167
|
-
const spy = sinon.spy();
|
168
|
-
sleep(0).then(spy);
|
169
|
-
expect(spy.callCount).toBe(0);
|
170
|
-
await afterEventLoop();
|
171
|
-
expect(spy.callCount).toBe(1);
|
172
|
-
});
|
173
|
-
|
174
|
-
test('Waits the appropriate ms', async () => {
|
175
|
-
const clock = sinon.useFakeTimers();
|
176
|
-
const spy = sinon.spy();
|
177
|
-
sleep(10).then(spy);
|
178
|
-
expect(spy.callCount).toBe(0);
|
179
|
-
clock.tick(10);
|
180
|
-
expect(spy.callCount).toBe(0);
|
181
|
-
clock.restore();
|
182
|
-
|
183
|
-
await afterEventLoop();
|
184
|
-
expect(spy.callCount).toBe(1);
|
185
|
-
});
|
186
|
-
});
|
@@ -66,6 +66,31 @@ describe('approximateWordCount', () => {
|
|
66
66
|
});
|
67
67
|
});
|
68
68
|
|
69
|
+
describe('sleep', () => {
|
70
|
+
const { sleep } = utils;
|
71
|
+
|
72
|
+
test('Sleep 0 doest not called immediately', async () => {
|
73
|
+
const spy = sinon.spy();
|
74
|
+
sleep(0).then(spy);
|
75
|
+
expect(spy.callCount).toBe(0);
|
76
|
+
await afterEventLoop();
|
77
|
+
expect(spy.callCount).toBe(1);
|
78
|
+
});
|
79
|
+
|
80
|
+
test('Waits the appropriate ms', async () => {
|
81
|
+
const clock = sinon.useFakeTimers();
|
82
|
+
const spy = sinon.spy();
|
83
|
+
sleep(10).then(spy);
|
84
|
+
expect(spy.callCount).toBe(0);
|
85
|
+
clock.tick(10);
|
86
|
+
expect(spy.callCount).toBe(0);
|
87
|
+
clock.restore();
|
88
|
+
|
89
|
+
await afterEventLoop();
|
90
|
+
expect(spy.callCount).toBe(1);
|
91
|
+
});
|
92
|
+
});
|
93
|
+
|
69
94
|
describe('toISO6391', () => {
|
70
95
|
const { toISO6391 } = utils;
|
71
96
|
|
@@ -23,6 +23,21 @@ describe('Search Provider', () => {
|
|
23
23
|
expect(provider.menuDetails).to.exist;
|
24
24
|
expect(provider.component).to.exist;
|
25
25
|
});
|
26
|
+
describe('BR calls', () => {
|
27
|
+
it('`advanceToPage', () => {
|
28
|
+
const provider = new searchProvider({
|
29
|
+
onProviderChange: sinon.fake(),
|
30
|
+
bookreader: {
|
31
|
+
leafNumToIndex: sinon.fake(),
|
32
|
+
_searchPluginGoToResult: sinon.fake()
|
33
|
+
}
|
34
|
+
});
|
35
|
+
|
36
|
+
provider.advanceToPage(1);
|
37
|
+
expect(provider?.bookreader.leafNumToIndex.callCount).to.equal(1);
|
38
|
+
expect(provider?.bookreader._searchPluginGoToResult.callCount).to.equal(1);
|
39
|
+
});
|
40
|
+
});
|
26
41
|
describe('Search request life cycles', () => {
|
27
42
|
it('Event: catches `BookReader:SearchStarted`', async() => {
|
28
43
|
const provider = new searchProvider({
|
@@ -91,6 +106,7 @@ describe('Search Provider', () => {
|
|
91
106
|
_searchPluginGoToResult: sinon.fake()
|
92
107
|
}
|
93
108
|
});
|
109
|
+
sinon.spy(provider, 'advanceToPage');
|
94
110
|
|
95
111
|
const searchResultStub = {
|
96
112
|
match: { par: [{ text: 'foo', page: 3 }] },
|
@@ -100,6 +116,7 @@ describe('Search Provider', () => {
|
|
100
116
|
{ detail: searchResultStub })
|
101
117
|
);
|
102
118
|
|
119
|
+
expect(provider.advanceToPage.callCount).to.equal(1);
|
103
120
|
expect(provider.bookreader._searchPluginGoToResult.callCount).to.equal(1);
|
104
121
|
});
|
105
122
|
});
|