@internetarchive/bookreader 5.0.0-40 → 5.0.0-40-a1
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.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
|
});
|