@internetarchive/bookreader 5.0.0-40-a1 → 5.0.0-42-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/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # 5.0.0-40
2
+ Fix: Better search highlights @cdrini
3
+ Dev: update lit 2 components @iisa
4
+ Dev: update lit @renovate
5
+
1
6
  # 5.0.0-39
2
7
  Fix: Performance improvements to scroll/zooming when text layer is larger @cdrini
3
8
  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-a1",
3
+ "version": "5.0.0-42-a1",
4
4
  "description": "The Internet Archive BookReader.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,7 +26,7 @@
26
26
  "private": false,
27
27
  "dependencies": {
28
28
  "@internetarchive/ia-activity-indicator": "^0.0.2",
29
- "@internetarchive/ia-item-navigator": "^1.0.0",
29
+ "@internetarchive/ia-item-navigator": "^1.0.1-a1",
30
30
  "@internetarchive/ia-sharing-options": "^1.0.1",
31
31
  "@internetarchive/icon-bookmark": "^1.3.2",
32
32
  "@internetarchive/icon-dl": "^1.3.3",
@@ -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.1.3"
41
+ "lit": "^2.2.2"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@babel/core": "7.17.5",
@@ -46,7 +46,7 @@
46
46
  "@babel/plugin-proposal-class-properties": "7.16.7",
47
47
  "@babel/plugin-proposal-decorators": "7.17.2",
48
48
  "@babel/preset-env": "7.16.11",
49
- "@open-wc/testing": "^3.1.2",
49
+ "@open-wc/testing": "^3.1.3",
50
50
  "@open-wc/testing-karma": "^4.0.9",
51
51
  "@types/jest": "^27.4.1",
52
52
  "@webcomponents/webcomponentsjs": "^2.6.0",
@@ -76,7 +76,7 @@
76
76
  "sinon": "^12.0.1",
77
77
  "soundmanager2": "2.97.20170602",
78
78
  "svgo": "2.4.0",
79
- "testcafe": "^1.18.4",
79
+ "testcafe": "^1.18.6",
80
80
  "testcafe-browser-provider-browserstack": "^1.13.2-alpha.1",
81
81
  "webpack": "5.51.1",
82
82
  "webpack-cli": "4.8.0"
@@ -1,6 +1,7 @@
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 */
4
5
 
5
6
  let searchState = {
6
7
  query: '',
@@ -28,10 +29,10 @@ export default class SearchProvider {
28
29
  this.bindEventListeners = this.bindEventListeners.bind(this);
29
30
  this.getMenuDetails = this.getMenuDetails.bind(this);
30
31
  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} */
35
36
  this.bookreader = bookreader;
36
37
  this.icon = html`<ia-icon-search style="width: var(--iconWidth); height: var(--iconHeight);"></ia-icon-search>`;
37
38
  this.label = 'Search inside';
@@ -174,13 +175,10 @@ export default class SearchProvider {
174
175
  `;
175
176
  }
176
177
 
178
+ /**
179
+ * @param {{ detail: {match: SearchInsideMatch} }} param0
180
+ */
177
181
  onSearchResultsClicked({ detail }) {
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);
182
+ this.bookreader._searchPluginGoToResult(detail.match.matchIndex);
185
183
  }
186
184
  }
@@ -103,6 +103,10 @@ 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");
106
110
  return rect;
107
111
  }
108
112
 
@@ -111,8 +115,9 @@ export function boxToSVGRect({ l: left, r: right, b: bottom, t: top }) {
111
115
  * @param {Array<{ l: number, r: number, b: number, t: number }>} boxes
112
116
  * @param {PageModel} page
113
117
  * @param {HTMLElement} containerEl
118
+ * @param {string[]} [rectClasses] CSS classes to add to the rects
114
119
  */
115
- export function renderBoxesInPageContainerLayer(layerClass, boxes, page, containerEl) {
120
+ export function renderBoxesInPageContainerLayer(layerClass, boxes, page, containerEl, rectClasses = null) {
116
121
  const mountedSvg = containerEl.querySelector(`.${layerClass}`);
117
122
  // Create the layer if it's not there
118
123
  const svg = mountedSvg || createSVGPageLayer(page, layerClass);
@@ -122,5 +127,12 @@ export function renderBoxesInPageContainerLayer(layerClass, boxes, page, contain
122
127
  if (imgEl) $(svg).insertAfter(imgEl);
123
128
  else $(svg).prependTo(containerEl);
124
129
  }
125
- boxes.forEach(box => svg.appendChild(boxToSVGRect(box)));
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
+ }
126
138
  }
@@ -238,3 +238,27 @@ 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,10 +1006,8 @@ BookReader.prototype.jumpToPage = function(pageNum) {
1006
1006
  * @param {PageIndex} index
1007
1007
  */
1008
1008
  BookReader.prototype._isIndexDisplayed = function(index) {
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;
1009
+ return this.displayedIndices ? this.displayedIndices.includes(index) :
1010
+ this.currentIndex() == index;
1013
1011
  };
1014
1012
 
1015
1013
  /**
@@ -31,9 +31,22 @@
31
31
  pointer-events: none;
32
32
 
33
33
  rect {
34
- fill: #0000ff;
35
- fill-opacity: 0.2;
36
- animation: hiliteFadeIn .2s;
34
+ // Note: Can't use fill-opacity ; safari inexplicably applies that to
35
+ // the outline as well
36
+ fill: rgba(0, 0, 255, 0.2);
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
+ }
37
50
  }
38
51
  }
39
52
 
@@ -207,8 +220,8 @@
207
220
  }
208
221
  }
209
222
 
210
- @keyframes hiliteFadeIn {
211
- from { fill-opacity: 0; }
223
+ @keyframes highlightFocus {
224
+ to { stroke-width: 20px; }
212
225
  }
213
226
 
214
227
  /* Mid size breakpoint */
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  /* global BookReader */
2
3
  /**
3
4
  * Plugin for Archive.org book search
@@ -23,6 +24,7 @@
23
24
  * @event BookReader:SearchCanceled - When no results found. Receives
24
25
  * `instance`
25
26
  */
27
+ import { poll } from '../../BookReader/utils.js';
26
28
  import { renderBoxesInPageContainerLayer } from '../../BookReader/PageContainer.js';
27
29
  import SearchView from './view.js';
28
30
  /** @typedef {import('../../BookReader/PageContainer').PageContainer} PageContainer */
@@ -112,7 +114,14 @@ BookReader.prototype._createPageContainer = (function (super_) {
112
114
  const pageContainer = super_.call(this, index);
113
115
  if (this.enableSearch && pageContainer.page && index in this._searchBoxesByIndex) {
114
116
  const pageIndex = pageContainer.page.index;
115
- renderBoxesInPageContainerLayer('searchHiliteLayer', this._searchBoxesByIndex[pageIndex], pageContainer.page, pageContainer.$container[0]);
117
+ const boxes = this._searchBoxesByIndex[pageIndex];
118
+ renderBoxesInPageContainerLayer(
119
+ 'searchHiliteLayer',
120
+ boxes,
121
+ pageContainer.page,
122
+ pageContainer.$container[0],
123
+ boxes.map(b => `match-index-${b.matchIndex}`),
124
+ );
116
125
  }
117
126
  return pageContainer;
118
127
  };
@@ -180,7 +189,7 @@ BookReader.prototype.search = async function(term = '', overrides = {}) {
180
189
 
181
190
  const url = `${baseUrl}${paramStr}`;
182
191
 
183
- const processSearchResults = (searchInsideResults) => {
192
+ const callSearchResultsCallback = (searchInsideResults) => {
184
193
  if (this.searchCancelled) {
185
194
  return;
186
195
  }
@@ -200,7 +209,7 @@ BookReader.prototype.search = async function(term = '', overrides = {}) {
200
209
  };
201
210
 
202
211
  this.trigger('SearchStarted', { term: this.searchTerm, instance: this });
203
- return processSearchResults(await $.ajax({
212
+ callSearchResultsCallback(await $.ajax({
204
213
  url: url,
205
214
  dataType: 'jsonp',
206
215
  cache: true,
@@ -242,10 +251,12 @@ BookReader.prototype.cancelSearchRequest = function () {
242
251
  * @property {number} b
243
252
  * @property {number} t
244
253
  * @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.
245
255
  */
246
256
 
247
257
  /**
248
258
  * @typedef {object} SearchInsideMatch
259
+ * @property {number} matchIndex This is a fake field! Not part of the API response. It is added by the JS.
249
260
  * @property {string} text
250
261
  * @property {Array<{ page: number, boxes: SearchInsideMatchBox[] }>} par
251
262
  */
@@ -259,19 +270,27 @@ BookReader.prototype.cancelSearchRequest = function () {
259
270
 
260
271
  /**
261
272
  * Search Results return handler
262
- * @callback
263
273
  * @param {SearchInsideResults} results
264
274
  * @param {object} options
265
275
  * @param {boolean} options.goToFirstResult
266
276
  */
267
277
  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
+ }
268
288
  this.searchResults = results || [];
269
289
 
270
290
  this.updateSearchHilites();
271
291
  this.removeProgressPopup();
272
292
  if (options.goToFirstResult) {
273
- const pageIndex = this._models.book.leafNumToIndex(results.matches[0].par[0].page);
274
- this._searchPluginGoToResult(pageIndex);
293
+ this._searchPluginGoToResult(0);
275
294
  }
276
295
  this.trigger('SearchCallback', { results, options, instance: this });
277
296
  };
@@ -316,7 +335,7 @@ BookReader.prototype._BRSearchCallbackError = function(results) {
316
335
  BookReader.prototype.updateSearchHilites = function() {
317
336
  /** @type {SearchInsideMatch[]} */
318
337
  const matches = this.searchResults?.matches || [];
319
- /** @type { {[pageIndex: number]: SearchInsideMatch[]} } */
338
+ /** @type { {[pageIndex: number]: SearchInsideMatchBox[]} } */
320
339
  const boxesByIndex = {};
321
340
 
322
341
  // Clear any existing svg layers
@@ -326,8 +345,8 @@ BookReader.prototype.updateSearchHilites = function() {
326
345
  for (const match of matches) {
327
346
  for (const box of match.par[0].boxes) {
328
347
  const pageIndex = this.leafNumToIndex(box.page);
329
- const pageMatches = boxesByIndex[pageIndex] || (boxesByIndex[pageIndex] = []);
330
- pageMatches.push(box);
348
+ const pageBoxes = boxesByIndex[pageIndex] || (boxesByIndex[pageIndex] = []);
349
+ pageBoxes.push(box);
331
350
  }
332
351
  }
333
352
 
@@ -336,7 +355,9 @@ BookReader.prototype.updateSearchHilites = function() {
336
355
  const pageIndex = parseFloat(pageIndexString);
337
356
  const page = this._models.book.getPage(pageIndex);
338
357
  const pageContainers = this.getActivePageContainerElementsForIndex(pageIndex);
339
- pageContainers.forEach(container => renderBoxesInPageContainerLayer('searchHiliteLayer', boxes, page, container));
358
+ for (const container of pageContainers) {
359
+ renderBoxesInPageContainerLayer('searchHiliteLayer', boxes, page, container, boxes.map(b => `match-index-${b.matchIndex}`));
360
+ }
340
361
  }
341
362
 
342
363
  this._searchBoxesByIndex = boxesByIndex;
@@ -354,11 +375,14 @@ BookReader.prototype.removeSearchHilites = function() {
354
375
  * Goes to the page specified. If the page is not viewable, tries to load the page
355
376
  * FIXME Most of this logic is IA specific, and should be less integrated into here
356
377
  * or at least more configurable.
357
- * @param {PageIndex} pageIndex
378
+ * @param {number} matchIndex
358
379
  */
359
- BookReader.prototype._searchPluginGoToResult = async function (pageIndex) {
380
+ BookReader.prototype._searchPluginGoToResult = async function (matchIndex) {
381
+ const match = this.searchResults?.matches[matchIndex];
382
+ const pageIndex = this.leafNumToIndex(match.par[0].page);
360
383
  const { book } = this._models;
361
384
  const page = book.getPage(pageIndex);
385
+ const onNearbyPage = Math.abs(this.currentIndex() - pageIndex) < 3;
362
386
  let makeUnviewableAtEnd = false;
363
387
  if (!page.isViewable) {
364
388
  const resp = await fetch('/services/bookreader/request_page?' + new URLSearchParams({
@@ -379,13 +403,35 @@ BookReader.prototype._searchPluginGoToResult = async function (pageIndex) {
379
403
  }
380
404
  }
381
405
  /* this updates the URL */
382
- this.suppressFragmentChange = false;
383
- this.jumpToIndex(pageIndex);
406
+ if (!this._isIndexDisplayed(pageIndex)) {
407
+ this.suppressFragmentChange = false;
408
+ this.jumpToIndex(pageIndex);
409
+ }
384
410
 
385
411
  // Reset it to unviewable if it wasn't resolved
386
412
  if (makeUnviewableAtEnd) {
387
413
  book.getPage(pageIndex).makeViewable(false);
388
414
  }
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
+ }
389
435
  };
390
436
 
391
437
  /**
@@ -260,7 +260,6 @@ class SearchView {
260
260
  <div>${uiStringPage} ${pageNumber}</div>
261
261
  </div>
262
262
  `)
263
- .data({ pageIndex })
264
263
  .appendTo(this.br.$('.BRnavline'))
265
264
  .on("mouseenter", (event) => {
266
265
  // remove from other markers then turn on just for this
@@ -277,12 +276,7 @@ class SearchView {
277
276
  $(event.target).addClass('front');
278
277
  })
279
278
  .on("mouseleave", (event) => $(event.target).removeClass('front'))
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));
279
+ .on("click", () => { this.br._searchPluginGoToResult(match.matchIndex); });
286
280
  });
287
281
  }
288
282
 
@@ -1,5 +1,5 @@
1
1
  import AbstractTTSEngine from './AbstractTTSEngine.js';
2
- import { sleep } from './utils.js';
2
+ import { sleep } from '../../BookReader/utils.js';
3
3
  /* global soundManager */
4
4
  import 'soundmanager2';
5
5
  import 'jquery.browser';
@@ -1,6 +1,7 @@
1
1
  /* global br */
2
2
  import { isChrome, isFirefox } from '../../util/browserSniffing.js';
3
- import { sleep, promisifyEvent, isAndroid } from './utils.js';
3
+ import { promisifyEvent, isAndroid } from './utils.js';
4
+ import { sleep } from '../../BookReader/utils.js';
4
5
  import AbstractTTSEngine from './AbstractTTSEngine.js';
5
6
  /** @typedef {import("./AbstractTTSEngine.js").PageChunk} PageChunk */
6
7
  /** @typedef {import("./AbstractTTSEngine.js").AbstractTTSSound} AbstractTTSSound */
@@ -26,15 +26,6 @@ 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
-
38
29
  /**
39
30
  * Checks whether the current browser is on android
40
31
  * @param {string} [userAgent]
@@ -1,5 +1,5 @@
1
1
  import BookReader from '@/src/BookReader';
2
- import { sleep } from '@/src/plugins/tts/utils';
2
+ import { sleep } from '@/src/BookReader/utils.js';
3
3
  import sinon from 'sinon';
4
4
 
5
5
  beforeAll(() => {
@@ -178,6 +178,15 @@ 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
+
181
190
  test('Handles no boxes', () => {
182
191
  const container = document.createElement('div');
183
192
  const page = { width: 100, height: 200 };
@@ -1,4 +1,5 @@
1
1
  import sinon from 'sinon';
2
+ import { afterEventLoop } from '../utils.js';
2
3
  import {
3
4
  clamp,
4
5
  cssPercentage,
@@ -8,8 +9,10 @@ import {
8
9
  escapeHTML,
9
10
  getActiveElement,
10
11
  isInputActive,
12
+ poll,
11
13
  polyfillCustomEvent,
12
14
  PolyfilledCustomEvent,
15
+ sleep,
13
16
  } from '@/src/BookReader/utils.js';
14
17
 
15
18
  test('clamp function returns Math.min(Math.max(value, min), max)', () => {
@@ -134,3 +137,50 @@ describe('PolyfilledCustomEvent', () => {
134
137
  expect(initCustomEventSpy.callCount).toBe(1);
135
138
  });
136
139
  });
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,31 +66,6 @@ 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
-
94
69
  describe('toISO6391', () => {
95
70
  const { toISO6391 } = utils;
96
71
 
@@ -23,21 +23,6 @@ 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
- });
41
26
  describe('Search request life cycles', () => {
42
27
  it('Event: catches `BookReader:SearchStarted`', async() => {
43
28
  const provider = new searchProvider({
@@ -106,7 +91,6 @@ describe('Search Provider', () => {
106
91
  _searchPluginGoToResult: sinon.fake()
107
92
  }
108
93
  });
109
- sinon.spy(provider, 'advanceToPage');
110
94
 
111
95
  const searchResultStub = {
112
96
  match: { par: [{ text: 'foo', page: 3 }] },
@@ -116,7 +100,6 @@ describe('Search Provider', () => {
116
100
  { detail: searchResultStub })
117
101
  );
118
102
 
119
- expect(provider.advanceToPage.callCount).to.equal(1);
120
103
  expect(provider.bookreader._searchPluginGoToResult.callCount).to.equal(1);
121
104
  });
122
105
  });