@internetarchive/bookreader 5.0.0-8 → 5.0.0-80

Sign up to get free protection for your applications and to get access to all the features.
Files changed (313) hide show
  1. package/.eslintrc.js +17 -15
  2. package/.github/workflows/node.js.yml +73 -10
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +398 -1133
  6. package/BookReader/BookReader.js +1 -1
  7. package/BookReader/BookReader.js.LICENSE.txt +20 -20
  8. package/BookReader/BookReader.js.map +1 -1
  9. package/BookReader/ia-bookreader-bundle.js +1782 -0
  10. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +7 -0
  11. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  12. package/BookReader/icons/1up.svg +1 -1
  13. package/BookReader/icons/2up.svg +1 -1
  14. package/BookReader/icons/advance.svg +1 -1
  15. package/BookReader/icons/chevron-right.svg +1 -1
  16. package/BookReader/icons/close-circle-dark.svg +1 -1
  17. package/BookReader/icons/close-circle.svg +1 -1
  18. package/BookReader/icons/fullscreen.svg +1 -1
  19. package/BookReader/icons/fullscreen_exit.svg +1 -1
  20. package/BookReader/icons/hamburger.svg +1 -1
  21. package/BookReader/icons/left-arrow.svg +1 -1
  22. package/BookReader/icons/magnify-minus.svg +1 -1
  23. package/BookReader/icons/magnify-plus.svg +1 -1
  24. package/BookReader/icons/magnify.svg +1 -1
  25. package/BookReader/icons/pause.svg +1 -1
  26. package/BookReader/icons/play.svg +1 -1
  27. package/BookReader/icons/playback-speed.svg +1 -1
  28. package/BookReader/icons/read-aloud.svg +1 -1
  29. package/BookReader/icons/review.svg +1 -1
  30. package/BookReader/icons/thumbnails.svg +1 -1
  31. package/BookReader/icons/voice.svg +1 -0
  32. package/BookReader/icons/volume-full.svg +1 -1
  33. package/BookReader/images/BRicons.svg +3 -3
  34. package/BookReader/images/books_graphic.svg +1 -1
  35. package/BookReader/images/icon_book.svg +1 -1
  36. package/BookReader/images/icon_bookmark.svg +1 -1
  37. package/BookReader/images/icon_gear.svg +1 -1
  38. package/BookReader/images/icon_hamburger.svg +1 -1
  39. package/BookReader/images/icon_home.svg +1 -1
  40. package/BookReader/images/icon_info.svg +1 -1
  41. package/BookReader/images/icon_one_page.svg +1 -1
  42. package/BookReader/images/icon_pause.svg +1 -1
  43. package/BookReader/images/icon_play.svg +1 -1
  44. package/BookReader/images/icon_playback-rate.svg +1 -1
  45. package/BookReader/images/icon_search_button.svg +1 -1
  46. package/BookReader/images/icon_share.svg +1 -1
  47. package/BookReader/images/icon_skip-ahead.svg +1 -1
  48. package/BookReader/images/icon_skip-back.svg +1 -1
  49. package/BookReader/images/icon_speaker.svg +1 -1
  50. package/BookReader/images/icon_speaker_open.svg +1 -1
  51. package/BookReader/images/icon_thumbnails.svg +1 -1
  52. package/BookReader/images/icon_toc.svg +1 -1
  53. package/BookReader/images/icon_two_pages.svg +1 -1
  54. package/BookReader/images/marker_chap-off.svg +1 -1
  55. package/BookReader/images/marker_chap-on.svg +1 -1
  56. package/BookReader/images/marker_srch-on.svg +1 -1
  57. package/BookReader/jquery-3.js +2 -0
  58. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  59. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  60. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  61. package/BookReader/plugins/plugin.autoplay.js +1 -1
  62. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  63. package/BookReader/plugins/plugin.chapters.js +25 -1
  64. package/BookReader/plugins/plugin.chapters.js.LICENSE.txt +1 -0
  65. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  66. package/BookReader/plugins/plugin.iframe.js +1 -1
  67. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  68. package/BookReader/plugins/plugin.iiif.js +2 -0
  69. package/BookReader/plugins/plugin.iiif.js.map +1 -0
  70. package/BookReader/plugins/plugin.resume.js +1 -1
  71. package/BookReader/plugins/plugin.resume.js.map +1 -1
  72. package/BookReader/plugins/plugin.search.js +2 -1
  73. package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
  74. package/BookReader/plugins/plugin.search.js.map +1 -1
  75. package/BookReader/plugins/plugin.text_selection.js +2 -1
  76. package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
  77. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  78. package/BookReader/plugins/plugin.tts.js +1 -1
  79. package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
  80. package/BookReader/plugins/plugin.tts.js.map +1 -1
  81. package/BookReader/plugins/plugin.url.js +1 -1
  82. package/BookReader/plugins/plugin.url.js.map +1 -1
  83. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  84. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  85. package/BookReader/webcomponents-bundle.js +3 -0
  86. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  87. package/BookReader/webcomponents-bundle.js.map +1 -0
  88. package/BookReaderDemo/BookReaderDemo.css +18 -19
  89. package/BookReaderDemo/BookReaderJSAdvanced.js +0 -3
  90. package/BookReaderDemo/BookReaderJSAutoplay.js +4 -1
  91. package/BookReaderDemo/BookReaderJSSimple.js +1 -0
  92. package/BookReaderDemo/IADemoBr.js +147 -0
  93. package/BookReaderDemo/demo-advanced.html +2 -2
  94. package/BookReaderDemo/demo-autoplay.html +2 -3
  95. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  96. package/BookReaderDemo/demo-fullscreen-mobile.html +3 -5
  97. package/BookReaderDemo/demo-fullscreen.html +2 -4
  98. package/BookReaderDemo/demo-iiif.html +99 -12
  99. package/BookReaderDemo/demo-internetarchive.html +214 -18
  100. package/BookReaderDemo/demo-multiple.html +2 -1
  101. package/BookReaderDemo/demo-preview-pages.html +2 -1
  102. package/BookReaderDemo/demo-simple.html +2 -1
  103. package/BookReaderDemo/demo-vendor-fullscreen.html +2 -4
  104. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  105. package/BookReaderDemo/immersion-1up.html +2 -2
  106. package/BookReaderDemo/immersion-mode.html +2 -4
  107. package/BookReaderDemo/toggle_controls.html +3 -2
  108. package/BookReaderDemo/view_mode.html +2 -1
  109. package/BookReaderDemo/viewmode-cycle.html +2 -3
  110. package/CHANGELOG.md +536 -33
  111. package/README.md +14 -1
  112. package/babel.config.js +20 -0
  113. package/codecov.yml +6 -0
  114. package/index.html +4 -1
  115. package/jsconfig.json +19 -0
  116. package/netlify.toml +9 -0
  117. package/package.json +70 -60
  118. package/renovate.json +52 -0
  119. package/scripts/preversion.js +0 -1
  120. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  121. package/src/BookNavigator/assets/button-base.js +4 -2
  122. package/src/BookNavigator/assets/ia-logo.js +17 -0
  123. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  124. package/src/BookNavigator/assets/icon_close.js +1 -1
  125. package/src/BookNavigator/book-navigator.js +590 -0
  126. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  127. package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
  128. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  129. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
  130. package/src/BookNavigator/bookmarks/bookmarks-provider.js +27 -17
  131. package/src/BookNavigator/bookmarks/ia-bookmarks.js +116 -67
  132. package/src/BookNavigator/delete-modal-actions.js +1 -1
  133. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  134. package/src/BookNavigator/downloads/downloads.js +41 -25
  135. package/src/BookNavigator/search/search-provider.js +49 -27
  136. package/src/BookNavigator/search/search-results.js +23 -9
  137. package/src/BookNavigator/sharing.js +27 -0
  138. package/src/BookNavigator/viewable-files.js +95 -0
  139. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -10
  140. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
  141. package/src/BookReader/BookModel.js +64 -34
  142. package/src/BookReader/DragScrollable.js +233 -0
  143. package/src/BookReader/Mode1Up.js +56 -351
  144. package/src/BookReader/Mode1UpLit.js +388 -0
  145. package/src/BookReader/Mode2Up.js +73 -1318
  146. package/src/BookReader/Mode2UpLit.js +776 -0
  147. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  148. package/src/BookReader/ModeSmoothZoom.js +312 -0
  149. package/src/BookReader/ModeThumb.js +18 -12
  150. package/src/BookReader/Navbar/Navbar.js +14 -40
  151. package/src/BookReader/PageContainer.js +81 -6
  152. package/src/BookReader/ReduceSet.js +1 -1
  153. package/src/BookReader/Toolbar/Toolbar.js +10 -37
  154. package/src/BookReader/events.js +2 -3
  155. package/src/BookReader/options.js +27 -2
  156. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  157. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  158. package/src/BookReader/utils/SelectionObserver.js +45 -0
  159. package/src/BookReader/utils.js +118 -13
  160. package/src/BookReader.js +427 -1061
  161. package/src/assets/icons/magnify-minus.svg +3 -7
  162. package/src/assets/icons/magnify-plus.svg +3 -7
  163. package/src/assets/icons/voice.svg +1 -0
  164. package/src/css/BookReader.scss +1 -5
  165. package/src/css/_BRBookmarks.scss +1 -1
  166. package/src/css/_BRComponent.scss +1 -1
  167. package/src/css/_BRmain.scss +16 -3
  168. package/src/css/_BRnav.scss +12 -39
  169. package/src/css/_BRpages.scss +149 -40
  170. package/src/css/_BRsearch.scss +68 -25
  171. package/src/css/_BRtoolbar.scss +5 -5
  172. package/src/css/_TextSelection.scss +87 -27
  173. package/src/css/_colorbox.scss +2 -2
  174. package/src/css/_controls.scss +20 -7
  175. package/src/css/_icons.scss +1 -1
  176. package/src/ia-bookreader/ia-bookreader.js +224 -0
  177. package/src/plugins/plugin.archive_analytics.js +3 -3
  178. package/src/plugins/plugin.autoplay.js +5 -11
  179. package/src/plugins/plugin.chapters.js +237 -191
  180. package/src/plugins/plugin.iiif.js +151 -0
  181. package/src/plugins/plugin.resume.js +3 -3
  182. package/src/plugins/plugin.text_selection.js +464 -134
  183. package/src/plugins/plugin.vendor-fullscreen.js +4 -4
  184. package/src/plugins/search/plugin.search.js +142 -125
  185. package/src/plugins/search/utils.js +43 -0
  186. package/src/plugins/search/view.js +33 -58
  187. package/src/plugins/tts/AbstractTTSEngine.js +71 -40
  188. package/src/plugins/tts/FestivalTTSEngine.js +13 -14
  189. package/src/plugins/tts/PageChunk.js +15 -21
  190. package/src/plugins/tts/PageChunkIterator.js +8 -12
  191. package/src/plugins/tts/WebTTSEngine.js +87 -71
  192. package/src/plugins/tts/plugin.tts.js +96 -127
  193. package/src/plugins/tts/utils.js +15 -25
  194. package/src/plugins/url/UrlPlugin.js +191 -0
  195. package/src/plugins/{plugin.url.js → url/plugin.url.js} +45 -16
  196. package/src/util/browserSniffing.js +22 -0
  197. package/src/util/docCookies.js +21 -2
  198. package/tests/e2e/README.md +37 -0
  199. package/tests/e2e/autoplay.test.js +2 -2
  200. package/tests/e2e/base.test.js +8 -16
  201. package/tests/e2e/helpers/base.js +53 -48
  202. package/tests/e2e/helpers/debug.js +1 -1
  203. package/tests/e2e/helpers/params.js +17 -0
  204. package/tests/e2e/helpers/rightToLeft.js +8 -14
  205. package/tests/e2e/helpers/search.js +73 -0
  206. package/tests/e2e/models/Navigation.js +20 -37
  207. package/tests/e2e/rightToLeft.test.js +4 -5
  208. package/tests/e2e/viewmode.test.js +40 -33
  209. package/tests/jest/BookNavigator/book-navigator.test.js +661 -0
  210. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  211. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  212. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  213. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  214. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  215. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  216. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  217. package/tests/{karma → jest}/BookNavigator/search/search-results.test.js +109 -60
  218. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  219. package/tests/jest/BookNavigator/viewable-files/viewable-files-provider.test.js +80 -0
  220. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  221. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +74 -14
  222. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
  223. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  224. package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
  225. package/tests/jest/BookReader/Mode2Up.test.js +98 -0
  226. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  227. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  228. package/tests/jest/BookReader/ModeSmoothZoom.test.js +218 -0
  229. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  230. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +10 -10
  231. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +88 -6
  232. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  233. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
  234. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  235. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  236. package/tests/jest/BookReader/utils/SelectionObserver.test.js +57 -0
  237. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  238. package/tests/jest/BookReader/utils.test.js +229 -0
  239. package/tests/jest/BookReader.keyboard.test.js +190 -0
  240. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
  241. package/tests/{BookReader.test.js → jest/BookReader.test.js} +26 -37
  242. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
  243. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +4 -4
  244. package/tests/jest/plugins/plugin.chapters.test.js +195 -0
  245. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
  246. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
  247. package/tests/jest/plugins/plugin.text_selection.test.js +317 -0
  248. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  249. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +25 -47
  250. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +39 -6
  251. package/tests/jest/plugins/search/utils.js +25 -0
  252. package/tests/jest/plugins/search/utils.test.js +29 -0
  253. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +29 -9
  254. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  255. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  256. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  257. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
  258. package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
  259. package/tests/jest/plugins/url/UrlPlugin.test.js +198 -0
  260. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +53 -14
  261. package/tests/jest/setup.js +3 -0
  262. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  263. package/tests/jest/util/docCookies.test.js +24 -0
  264. package/tests/{util → jest/util}/strings.test.js +1 -1
  265. package/tests/{utils.js → jest/utils.js} +38 -0
  266. package/webpack.config.js +12 -6
  267. package/.babelrc +0 -12
  268. package/.dependabot/config.yml +0 -6
  269. package/.testcaferc.json +0 -5
  270. package/BookReader/bookreader-component-bundle.js +0 -1450
  271. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  272. package/BookReader/bookreader-component-bundle.js.map +0 -1
  273. package/BookReader/jquery-1.10.1.js +0 -2
  274. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  275. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  276. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  277. package/BookReader/plugins/plugin.mobile_nav.js +0 -2
  278. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  279. package/BookReaderDemo/IIIFBookReader.js +0 -207
  280. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  281. package/BookReaderDemo/demo-iiif.js +0 -26
  282. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  283. package/karma.conf.js +0 -23
  284. package/src/BookNavigator/BookModel.js +0 -14
  285. package/src/BookNavigator/BookNavigator.js +0 -446
  286. package/src/BookNavigator/assets/book-loader.js +0 -27
  287. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  288. package/src/BookNavigator/search/a-search-result.js +0 -55
  289. package/src/BookReader/DebugConsole.js +0 -54
  290. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  291. package/src/ItemNavigator/ItemNavigator.js +0 -376
  292. package/src/ItemNavigator/providers/sharing.js +0 -29
  293. package/src/css/_MobileNav.scss +0 -194
  294. package/src/dragscrollable-br.js +0 -261
  295. package/src/lit-wrapper.js +0 -2
  296. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  297. package/src/plugins/plugin.mobile_nav.js +0 -287
  298. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  299. package/tests/BookReader/DebugConsole.test.js +0 -25
  300. package/tests/BookReader/Mode1Up.test.js +0 -164
  301. package/tests/BookReader/Mode2Up.test.js +0 -247
  302. package/tests/BookReader/utils.test.js +0 -109
  303. package/tests/e2e/helpers/desktopSearch.js +0 -72
  304. package/tests/e2e/helpers/mobileSearch.js +0 -85
  305. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  306. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  307. package/tests/karma/BookNavigator/search/search-provider.test.js +0 -23
  308. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  309. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  310. package/tests/plugins/plugin.chapters.test.js +0 -130
  311. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  312. package/tests/plugins/plugin.text_selection.test.js +0 -203
  313. package/tests/util/docCookies.test.js +0 -15
@@ -0,0 +1,317 @@
1
+ import sinon from 'sinon';
2
+
3
+ import '@/src/BookReader.js';
4
+ import {
5
+ BookreaderWithTextSelection,
6
+ Cache,
7
+ genMap,
8
+ lookAroundWindow,
9
+ zip,
10
+ } from '@/src/plugins/plugin.text_selection.js';
11
+
12
+
13
+ /** @type {BookReader} */
14
+
15
+ // djvu.xml book infos copied from https://ia803103.us.archive.org/14/items/goodytwoshoes00newyiala/goodytwoshoes00newyiala_djvu.xml
16
+ const FAKE_XML_1WORD = `
17
+ <OBJECT data="file://localhost//tmp/derive/goodytwoshoes00newyiala//goodytwoshoes00newyiala.djvu" height="3192" type="image/x.djvu" usemap="goodytwoshoes00newyiala_0001.djvu" width="2454">
18
+ <PARAGRAPH>
19
+ <LINE>
20
+ <WORD coords="1216,2768,1256,2640">test</WORD>
21
+ </LINE>
22
+ </PARAGRAPH>
23
+ </OBJECT>`;
24
+ const FAKE_XML_MULT_WORDS = `
25
+ <OBJECT data="file://localhost//tmp/derive/goodytwoshoes00newyiala//goodytwoshoes00newyiala.djvu" height="3192" type="image/x.djvu" usemap="goodytwoshoes00newyiala_0001.djvu" width="2454">
26
+ <PARAGRAPH>
27
+ <LINE>
28
+ <WORD coords="1216,2768,1256,2640">test1</WORD>
29
+ <WORD coords="1400,2768,1500,2640">test2</WORD>
30
+ <WORD coords="1600,2768,1700,2640">test3</WORD>
31
+ </LINE>
32
+ </PARAGRAPH>
33
+ </OBJECT>`;
34
+ const FAKE_XML_MULT_LINES = `
35
+ <OBJECT data="file://localhost//tmp/derive/goodytwoshoes00newyiala//goodytwoshoes00newyiala.djvu" height="3192" type="image/x.djvu" usemap="goodytwoshoes00newyiala_0001.djvu" width="2454">
36
+ <PARAGRAPH>
37
+ <LINE>
38
+ <WORD coords="119,2050,230,2014" x-confidence="29">way </WORD>
39
+ <WORD coords="230,2038,320,2002" x-confidence="30">can </WORD>
40
+ <WORD coords="320,2039,433,2002" x-confidence="28">false </WORD>
41
+ <WORD coords="433,2051,658,2003" x-confidence="29">judgment </WORD>
42
+ <WORD coords="658,2039,728,2002" x-confidence="30">be </WORD>
43
+ <WORD coords="658,2039,728,2002" x-confidence="30">-</WORD>
44
+ <WORD coords="728,2039,939,2001" x-confidence="29">formed. </WORD>
45
+ <WORD coords="939,2039,1087,2001" x-confidence="29">There </WORD>
46
+ <WORD coords="1087,2039,1187,2002" x-confidence="29">still </WORD>
47
+ <WORD coords="1187,2038,1370,2003" x-confidence="29">remains </WORD>
48
+ <WORD coords="1370,2037,1433,2014" x-confidence="28">an-</WORD>
49
+ </LINE>
50
+ <LINE>
51
+ <WORD coords="244,2099,370,2063" x-confidence="29">other mode </WORD>
52
+ <WORD coords="370,2100,427,2064" x-confidence="29">in </WORD>
53
+ <WORD coords="427,2100,566,2063" x-confidence="29">which </WORD>
54
+ <WORD coords="566,2100,670,2063" x-confidence="29">false </WORD>
55
+ <WORD coords="670,2112,907,2063" x-confidence="29">judgments </WORD>
56
+ <WORD coords="907,2112,1006,2064" x-confidence="29">may </WORD>
57
+ <WORD coords="1006,2100,1071,2063" x-confidence="29">be </WORD>
58
+ <WORD coords="1071,2100,1266,2062" x-confidence="29">formed. </WORD>
59
+ <WORD coords="1266,2110,1435,2062" x-confidence="29">Suppose</WORD>
60
+ </LINE>
61
+ <LINE>
62
+ <WORD coords="118,2160,217,2123" x-confidence="29">that </WORD>
63
+ <WORD coords="217,2160,289,2124" x-confidence="29">we </WORD>
64
+ <WORD coords="289,2160,400,2124" x-confidence="29">have </WORD>
65
+ <WORD coords="400,2160,456,2124" x-confidence="30">in </WORD>
66
+ <WORD coords="456,2161,542,2136" x-confidence="29">our </WORD>
67
+ <WORD coords="542,2161,660,2124" x-confidence="29">souls </WORD>
68
+ <WORD coords="660,2160,700,2136" x-confidence="29">a </WORD>
69
+ <WORD coords="700,2160,847,2129" x-confidence="28">waxen </WORD>
70
+ <WORD coords="847,2160,983,2123" x-confidence="29">tablet </WORD>
71
+ <WORD coords="983,2160,1045,2124" x-confidence="29">of </WORD>
72
+ <WORD coords="1045,2160,1211,2124" x-confidence="29">various </WORD>
73
+ <WORD coords="1211,2171,1398,2122" x-confidence="29">qualities </WORD>
74
+ <WORD coords="1398,2157,1434,2122" x-confidence="29">lastWord</WORD>
75
+ </LINE>
76
+ </PARAGRAPH>
77
+ </OBJECT>`;
78
+ const FAKE_XML_5COORDS = `
79
+ <OBJECT data="file://localhost//tmp/derive/goodytwoshoes00newyiala//goodytwoshoes00newyiala.djvu" height="3192" type="image/x.djvu" usemap="goodytwoshoes00newyiala_0001.djvu" width="2454">
80
+ <PARAGRAPH>
81
+ <LINE>
82
+ <WORD coords="1216,2768,1256,2640,2690">test</WORD>
83
+ </LINE>
84
+ </PARAGRAPH>
85
+ </OBJECT>`;
86
+ const FAKE_XML_EMPTY = '';
87
+
88
+ describe("Generic tests", () => {
89
+ document.body.innerHTML = '<div id="BookReader">';
90
+ const br = new BookreaderWithTextSelection({
91
+ data: [
92
+ [
93
+ { width: 800, height: 1200,
94
+ uri: '//archive.org/download/BookReader/img/page001.jpg' },
95
+ ],
96
+ [
97
+ { width: 800, height: 1200,
98
+ uri: '//archive.org/download/BookReader/img/page002.jpg' },
99
+ { width: 800, height: 1200,
100
+ uri: '//archive.org/download/BookReader/img/page003.jpg' },
101
+ ],
102
+ [
103
+ { width: 800, height: 1200,
104
+ uri: '//archive.org/download/BookReader/img/page004.jpg' },
105
+ { width: 800, height: 1200,
106
+ uri: '//archive.org/download/BookReader/img/page005.jpg' },
107
+ ]
108
+ ],
109
+ });
110
+ br.init();
111
+
112
+ afterEach(() => {
113
+ sinon.restore();
114
+ $('.BRtextLayer').remove();
115
+ });
116
+
117
+ test("_createPageContainer overridden function still creates a BRpagecontainer element", () => {
118
+ const spy = sinon.spy(br.textSelectionPlugin, 'createTextLayer');
119
+ sinon.stub(br.textSelectionPlugin, "getPageText")
120
+ .returns($(new DOMParser().parseFromString(FAKE_XML_1WORD, "text/xml")));
121
+ const container = br._createPageContainer(1, {});
122
+ expect(container).toBeTruthy();
123
+ expect(spy.callCount).toBe(1);
124
+ });
125
+
126
+ // test loading first object from sample data
127
+ test("_createPageContainer handles index 0", () => {
128
+ const spy = sinon.spy(br.textSelectionPlugin, 'createTextLayer');
129
+ br._createPageContainer(0, {});
130
+ expect(spy.callCount).toBe(1);
131
+ });
132
+
133
+ // test loading last object from sample data
134
+ test("_createPageContainer handles index -1", () => {
135
+ const spy = sinon.spy(br.textSelectionPlugin, 'createTextLayer');
136
+ br._createPageContainer(-1, {});
137
+ expect(spy.callCount).toBe(0);
138
+ });
139
+
140
+ test("createTextLayer will render the last page and create text layer properly", async () => {
141
+ const $container = br.refs.$brContainer;
142
+ sinon.stub(br.textSelectionPlugin, "getPageText")
143
+ .returns($(new DOMParser().parseFromString(FAKE_XML_1WORD, "text/xml")));
144
+ const pageIndex = br.data.length - 1;
145
+ await br.textSelectionPlugin.createTextLayer({ $container, page: { index: pageIndex, width: 100, height: 100 }});
146
+ expect($container.find(".BRtextLayer").length).toBe(1);
147
+ expect($container.find("p").length).toBe(1);
148
+ });
149
+
150
+ test("createTextLayer will not create text layer if there are too many words", async () => {
151
+ const $container = br.refs.$brContainer;
152
+ const xml = FAKE_XML_1WORD.replace(/<WORD.*<\/WORD>/, FAKE_XML_1WORD.match(/<WORD.*<\/WORD>/)[0].repeat(3000));
153
+ sinon.stub(br.textSelectionPlugin, "getPageText")
154
+ .returns($(new DOMParser().parseFromString(xml, "text/xml")));
155
+ await br.textSelectionPlugin.createTextLayer({ $container, page: { index: 0, width: 100, height: 100 }});
156
+ expect($container.find(".BRtextLayer").length).toBe(0);
157
+ expect($container.find("p").length).toBe(0);
158
+ expect($container.find(".BRwordElement").length).toBe(0);
159
+ });
160
+
161
+ test("createTextLayer creates text layer with paragraph with 1 word element", async () => {
162
+ const $container = br.refs.$brContainer;
163
+ sinon.stub(br.textSelectionPlugin, "getPageText")
164
+ .returns($(new DOMParser().parseFromString(FAKE_XML_1WORD, "text/xml")));
165
+ await br.textSelectionPlugin.createTextLayer({ $container, page: { index: 1, width: 100, height: 100 }});
166
+ expect($container.find(".BRtextLayer").length).toBe(1);
167
+ expect($container.find("p").length).toBe(1);
168
+ expect($container.find(".BRwordElement").length).toBe(1);
169
+ expect($container.find(".BRwordElement").text()).toBe("test");
170
+ });
171
+
172
+ test("createTextLayer creates text layer with paragraph with multiple word elements", async () => {
173
+ const $container = br.refs.$brContainer;
174
+ sinon.stub(br.textSelectionPlugin, "getPageText")
175
+ .returns($(new DOMParser().parseFromString(FAKE_XML_MULT_WORDS, "text/xml")));
176
+ await br.textSelectionPlugin.createTextLayer({ $container, page: { index: 2, width: 100, height: 100 }});
177
+ expect($container.find(".BRtextLayer").length).toBe(1);
178
+ expect($container.find("p").length).toBe(1);
179
+ expect($container.find(".BRwordElement").length).toBe(3);
180
+ expect($container.find(".BRspace").length).toBe(2);
181
+ });
182
+
183
+ test("createTextLayer creates text layer with paragraph with word with 5 params coordinates", async () => {
184
+ const $container = br.refs.$brContainer;
185
+ sinon.stub(br.textSelectionPlugin, "getPageText")
186
+ .returns($(new DOMParser().parseFromString(FAKE_XML_5COORDS, "text/xml")));
187
+ await br.textSelectionPlugin.createTextLayer({ $container, page: { index: 3, width: 100, height: 100 }});
188
+ expect($container.find(".BRtextLayer").length).toBe(1);
189
+ expect($container.find("p").length).toBe(1);
190
+ expect($container.find(".BRwordElement").length).toBe(1);
191
+ });
192
+
193
+ test("createTextLayer handles multiple lines", async () => {
194
+ const $container = br.refs.$brContainer;
195
+ sinon.stub(br.textSelectionPlugin, "getPageText")
196
+ .returns($(new DOMParser().parseFromString(FAKE_XML_MULT_LINES, "text/xml")));
197
+ await br.textSelectionPlugin.createTextLayer({ $container, page: { index: 3, width: 100, height: 100 }});
198
+ expect($container.find(".BRtextLayer").length).toBe(1);
199
+ expect($container.find("p").length).toBe(1);
200
+ expect($container.find(".BRlineElement").length).toBe(3);
201
+ // Adds space at end of line; except last line/hyphens
202
+ expect($container.find("p").text()).toMatch(/another/);
203
+ expect($container.find("p").text()).toMatch(/Suppose /);
204
+ expect($container.find("p").text()).toMatch(/lastWord$/);
205
+ expect($container.find("p > br").length).toBe(1);
206
+ });
207
+
208
+ test("createTextLayer repairs trailing hyphens", async () => {
209
+ const $container = br.refs.$brContainer;
210
+ sinon.stub(br.textSelectionPlugin, "getPageText")
211
+ .returns($(new DOMParser().parseFromString(FAKE_XML_MULT_LINES, "text/xml")));
212
+ await br.textSelectionPlugin.createTextLayer({ $container, page: { index: 3, width: 100, height: 100 }});
213
+
214
+ expect($container.find(".BRwordElement--hyphen").length).toBe(1);
215
+ expect($container.find(".BRwordElement--hyphen").closest(".BRlineElement").text().endsWith(' ')).toBe(false);
216
+ expect($container.find(".BRwordElement--hyphen").closest(".BRlineElement").text().endsWith('-')).toBe(false);
217
+ });
218
+
219
+ test("createTextLayer can handle empty xml", async () => {
220
+ const $container = br.refs.$brContainer;
221
+ sinon.stub(br.textSelectionPlugin, "getPageText")
222
+ .returns($(new DOMParser().parseFromString(FAKE_XML_EMPTY, "text/xml")));
223
+ await br.textSelectionPlugin.createTextLayer({ $container, page: { index: 4, width: 100, height: 100 }});
224
+ expect($container.find(".BRtextLayer").length).toBe(1);
225
+ expect($container.find("p").length).toBe(0);
226
+ expect($container.find(".BRwordElement").length).toBe(0);
227
+ });
228
+
229
+ const LONG_PRESS_DURATION = 500;
230
+ test("calling stopPageFlip does not allow long click to flip the page", () => {
231
+ const $container = br.refs.$brContainer;
232
+ br.textSelectionPlugin.stopPageFlip($container);
233
+ const currIndex = br.currentIndex();
234
+ $container.find("BRwordElement").trigger("mousedown");
235
+ // Waits for long press
236
+ setTimeout(() => {
237
+ $container.find("BRwordElement").trigger("mousedown");
238
+ // Waits for flipping animation
239
+ setTimeout(() => {
240
+ expect(br.currentIndex()).toBe(currIndex);
241
+ }, 2000);
242
+ }, LONG_PRESS_DURATION);
243
+ });
244
+ });
245
+
246
+ describe("Cache", () => {
247
+ test('Adding works', () => {
248
+ const c = new Cache(10);
249
+ c.add(35);
250
+ expect(c.entries).toEqual([35]);
251
+ });
252
+
253
+ test('Size does not grow beyond limit', () => {
254
+ const c = new Cache(2);
255
+ c.add(35);
256
+ c.add(32);
257
+ c.add(12);
258
+ c.add(11);
259
+ c.add(112);
260
+ expect(c.entries).toHaveLength(2);
261
+ });
262
+
263
+ test('Oldest evicted first', () => {
264
+ const c = new Cache(2);
265
+ c.add(35);
266
+ c.add(32);
267
+ c.add(12);
268
+ c.add(12);
269
+ c.add(10);
270
+ expect(c.entries).toEqual([12, 10]);
271
+ });
272
+ });
273
+
274
+ describe('genMap', () => {
275
+ test('handles empty', () => {
276
+ expect(Array.from(genMap([], x => x ** 2))).toEqual([]);
277
+ });
278
+
279
+ test('handles non-empty', () => {
280
+ expect(Array.from(genMap([1,2,3], x => x ** 2))).toEqual([1,4,9]);
281
+ });
282
+ });
283
+
284
+ describe('lookAroundWindow', () => {
285
+ test('handles empty', () => {
286
+ expect(Array.from(lookAroundWindow([]))).toEqual([]);
287
+ });
288
+
289
+ test('handles smaller than window', () => {
290
+ expect(Array.from(lookAroundWindow([1]))).toEqual([[undefined, 1, undefined]]);
291
+ expect(Array.from(lookAroundWindow([1, 2]))).toEqual([[undefined, 1, 2], [1, 2, undefined]]);
292
+ expect(Array.from(lookAroundWindow([1, 2, 3]))).toEqual([[undefined, 1, 2], [1, 2, 3], [2, 3, undefined]]);
293
+ });
294
+
295
+ test('handles larger than window', () => {
296
+ expect(Array.from(lookAroundWindow([1, 2, 3, 4]))).toEqual([
297
+ [undefined, 1, 2],
298
+ [1, 2, 3],
299
+ [2, 3, 4],
300
+ [3, 4, undefined],
301
+ ]);
302
+ });
303
+ });
304
+
305
+ describe('zip', () => {
306
+ test('handles empty', () => {
307
+ expect(Array.from(zip([], []))).toEqual([]);
308
+ });
309
+
310
+ test('uneven throws error', () => {
311
+ expect(() => Array.from(zip([1], [2, 3]))).toThrow();
312
+ });
313
+
314
+ test('handles even', () => {
315
+ expect(Array.from(zip([1, 2], [3, 4]))).toEqual([[1, 3], [2, 4]]);
316
+ });
317
+ });
@@ -1,6 +1,6 @@
1
1
 
2
- import BookReader from '../../src/BookReader.js';
3
- import * as util from '../../src/plugins/plugin.vendor-fullscreen.js';
2
+ import BookReader from '@/src/BookReader.js';
3
+ import * as util from '@/src/plugins/plugin.vendor-fullscreen.js';
4
4
 
5
5
  let br;
6
6
  beforeEach(() => {
@@ -1,9 +1,9 @@
1
+ import BookReader from '@/src/BookReader.js';
2
+ import '@/src/plugins/search/plugin.search.js';
3
+ import { deepCopy } from '../../utils.js';
4
+ import { DUMMY_RESULTS } from './utils.js';
1
5
 
2
- import BookReader from '../../../src/BookReader.js';
3
- import '../../../src/plugins/plugin.mobile_nav.js';
4
- import '../../../src/plugins/search/plugin.search.js';
5
-
6
- jest.mock('../../../src/plugins/search/view.js');
6
+ jest.mock('@/src/plugins/search/view.js');
7
7
 
8
8
  let br;
9
9
  const namespace = 'BookReader:';
@@ -18,27 +18,7 @@ beforeEach(() => {
18
18
  $.ajax = jest.fn().mockImplementation(() => {
19
19
  // return from:
20
20
  // `https://ia800304.us.archive.org/fulltext/inside.php?item_id=adventuresofoli00dick&doc=adventuresofoli00dick&path=/30/items/adventuresofoli00dick&q=child&callback=<serialized jQ CB>`
21
- return Promise.resolve({
22
- ia: "adventuresofoli00dick",
23
- q: "child",
24
- indexed: true,
25
- page_count: 644,
26
- body_length: 666,
27
- leaf0_missing: false,
28
- matches: [{
29
- text: 'For a long; time after it was ushered into this world of sorrow and trouble, by the parish surgeon, it remained a matter of considerable doubt wliether the {{{child}}} Avould survi^ e to bear any name at all; in which case it is somewhat more than probable that these memoirs would never have appeared; or, if they had, that being comprised within a couple of pages, they would have possessed the inestimable meiit of being the most concise and faithful specimen of biography, extant in the literature of any age or country.',
30
- par: [{
31
- boxes: [{r: 1221, b: 2121, t: 2075, page: 37, l: 1107}],
32
- b: 2535,
33
- t: 1942,
34
- page_width: 1790,
35
- r: 1598,
36
- l: 50,
37
- page_height: 2940,
38
- page: 37
39
- }]
40
- }]
41
- });
21
+ return Promise.resolve(deepCopy(DUMMY_RESULTS));
42
22
  });
43
23
 
44
24
  $.fn.trigger = jest.fn();
@@ -102,7 +82,7 @@ describe('Plugin: Search', () => {
102
82
  expect(br.search.mock.calls[0][1])
103
83
  .toHaveProperty('goToFirstResult', true);
104
84
  expect(br.search.mock.calls[0][1])
105
- .toHaveProperty('suppressFragmentChange', true);
85
+ .toHaveProperty('suppressFragmentChange', false);
106
86
  });
107
87
 
108
88
  test('calling `search` fires ajax call`', () => {
@@ -117,28 +97,30 @@ describe('Plugin: Search', () => {
117
97
  expect(triggeredEvents()).toContain(`${namespace}SearchStarted`);
118
98
  });
119
99
 
120
- test('SearchCallback event fires when AJAX search returns results', () => {
100
+ test('SearchStarted event fires and should go to first result', () => {
121
101
  br.init();
122
- const dfd = br.search('foo');
123
- return dfd.then(() => {
124
- expect(triggeredEvents()).toContain(`${namespace}SearchCallback`);
125
- });
102
+ br.search('foo', { goToFirstResult: true});
103
+ expect(br.options.goToFirstResult).toBeTruthy();
126
104
  });
127
105
 
128
- test('SearchCallbackError event fires when AJAX search returns error', () => {
106
+ test('SearchCallback event fires when AJAX search returns results', async () => {
107
+ br.init();
108
+ await br.search('foo');
109
+ expect(triggeredEvents()).toContain(`${namespace}SearchCallback`);
110
+ });
111
+
112
+ test('SearchCallbackError event fires when AJAX search returns error', async () => {
129
113
  $.ajax = jest.fn().mockImplementation(() => {
130
114
  return Promise.resolve({
131
115
  error: true,
132
116
  });
133
117
  });
134
118
  br.init();
135
- const dfd = br.search('foo');
136
- return dfd.then(() => {
137
- expect(triggeredEvents()).toContain(`${namespace}SearchCallbackError`);
138
- });
119
+ await br.search('foo');
120
+ expect(triggeredEvents()).toContain(`${namespace}SearchCallbackError`);
139
121
  });
140
122
 
141
- test('SearchCallbackNotIndexed event fires when AJAX search returns false indexed value', () => {
123
+ test('SearchCallbackNotIndexed event fires when AJAX search returns false indexed value', async () => {
142
124
  $.ajax = jest.fn().mockImplementation(() => {
143
125
  return Promise.resolve({
144
126
  matches: [],
@@ -146,22 +128,18 @@ describe('Plugin: Search', () => {
146
128
  });
147
129
  });
148
130
  br.init();
149
- const dfd = br.search('foo');
150
- return dfd.then(() => {
151
- expect(triggeredEvents()).toContain(`${namespace}SearchCallbackBookNotIndexed`);
152
- });
131
+ await br.search('foo');
132
+ expect(triggeredEvents()).toContain(`${namespace}SearchCallbackBookNotIndexed`);
153
133
  });
154
134
 
155
- test('SearchCallbackEmpty event fires when AJAX search returns no matches', () => {
135
+ test('SearchCallbackEmpty event fires when AJAX search returns no matches', async () => {
156
136
  $.ajax = jest.fn().mockImplementation(() => {
157
137
  return Promise.resolve({
158
138
  matches: [],
159
139
  });
160
140
  });
161
141
  br.init();
162
- const dfd = br.search('foo');
163
- return dfd.then(() => {
164
- expect(triggeredEvents()).toContain(`${namespace}SearchCallbackEmpty`);
165
- });
142
+ await br.search('foo');
143
+ expect(triggeredEvents()).toContain(`${namespace}SearchCallbackEmpty`);
166
144
  });
167
145
  });
@@ -1,8 +1,8 @@
1
1
 
2
- import BookReader from '../../../src/BookReader.js';
3
- import '../../../src/plugins/plugin.mobile_nav.js';
4
- import '../../../src/plugins/search/plugin.search.js';
5
- import '../../../src/plugins/search/view.js';
2
+ import BookReader from '@/src/BookReader.js';
3
+ import '@/src/plugins/search/plugin.search.js';
4
+ import { marshallSearchResults } from '@/src/plugins/search/utils.js';
5
+ import '@/src/plugins/search/view.js';
6
6
 
7
7
  let br;
8
8
  const namespace = 'BookReader:';
@@ -27,6 +27,31 @@ const results = {
27
27
  }]
28
28
  }]
29
29
  };
30
+
31
+ marshallSearchResults(results, () => '', '{{{', '}}}');
32
+ const resultWithScript = {
33
+ ia: "adventuresofoli00dick",
34
+ q: "child",
35
+ indexed: true,
36
+ page_count: 644,
37
+ body_length: 666,
38
+ leaf0_missing: false,
39
+ matches: [{
40
+ text: 'foo bar <script>alert(1);</script> {{{keyword}}} baz',
41
+ par: [{
42
+ boxes: [{r: 1221, b: 2121, t: 2075, page: 37, l: 1107}],
43
+ b: 2535,
44
+ t: 1942,
45
+ page_width: 1790,
46
+ r: 1598,
47
+ l: 50,
48
+ page_height: 2940,
49
+ page: 37
50
+ }]
51
+ }]
52
+ };
53
+
54
+ marshallSearchResults(resultWithScript, () => '', '{{{', '}}}');
30
55
  beforeEach(() => {
31
56
  $.ajax = jest.fn().mockImplementation(() => {
32
57
  // return from:
@@ -63,14 +88,14 @@ describe('View: Plugin: Search', () => {
63
88
 
64
89
  expect(br.searchView.dom.searchNavigation).toBeUndefined();
65
90
 
66
- br.searchView.handleSearchCallback(event, { results, options})
91
+ br.searchView.handleSearchCallback(event, { results, options});
67
92
  expect(br.searchView.dom.searchNavigation).toBeDefined();
68
93
  });
69
94
  test('has controls', () => {
70
95
  br.init();
71
96
  const event = new CustomEvent(`${namespace}SearchCallback`);
72
97
  const options = { goToFirstResult: false };
73
- br.searchView.handleSearchCallback(event, { results, options})
98
+ br.searchView.handleSearchCallback(event, { results, options});
74
99
 
75
100
  const searchResultsNav = document.querySelector('.BRsearch-navigation');
76
101
  expect(searchResultsNav).toBeDefined();
@@ -81,6 +106,14 @@ describe('View: Plugin: Search', () => {
81
106
  expect(searchResultsNav.querySelector('.prev')).toBeDefined();
82
107
  expect(searchResultsNav.querySelector('.next')).toBeDefined();
83
108
  });
109
+ test('disallows xss from search results', () => {
110
+ br.init();
111
+ const event = new CustomEvent(`${namespace}SearchCallback`);
112
+ const options = { goToFirstResult: false };
113
+ br.searchView.handleSearchCallback(event, { results: resultWithScript, options });
114
+
115
+ expect(br.searchView.dom.searchNavigation.parent().html()).not.toContain('<script>alert(1);</script>');
116
+ });
84
117
 
85
118
  describe('Click events handlers', () => {
86
119
  it('triggers custom event when toggling side menu', () => {
@@ -0,0 +1,25 @@
1
+ import { marshallSearchResults } from "@/src/plugins/search/utils";
2
+
3
+ export const DUMMY_RESULTS = {
4
+ ia: "adventuresofoli00dick",
5
+ q: "child",
6
+ indexed: true,
7
+ page_count: 644,
8
+ body_length: 666,
9
+ leaf0_missing: false,
10
+ matches: [{
11
+ text: 'For a long; time after it was ushered into this world of sorrow and trouble, by the parish surgeon, it remained a matter of considerable doubt wliether the {{{child}}} Avould survi^ e to bear any name at all; in which case it is somewhat more than probable that these memoirs would never have appeared; or, if they had, that being comprised within a couple of pages, they would have possessed the inestimable meiit of being the most concise and faithful specimen of biography, extant in the literature of any age or country.',
12
+ par: [{
13
+ boxes: [{r: 1221, b: 2121, t: 2075, page: 37, l: 1107}],
14
+ b: 2535,
15
+ t: 1942,
16
+ page_width: 1790,
17
+ r: 1598,
18
+ l: 50,
19
+ page_height: 2940,
20
+ page: 37
21
+ }]
22
+ }]
23
+ };
24
+
25
+ marshallSearchResults(DUMMY_RESULTS, () => '', '{{{', '}}}');
@@ -0,0 +1,29 @@
1
+ import { marshallSearchResults, renderMatch } from '@/src/plugins/search/utils.js';
2
+ import { deepCopy } from '@/tests/jest/utils.js';
3
+ import { DUMMY_RESULTS } from './utils.js';
4
+
5
+ describe('renderMatch', () => {
6
+ test('Supports custom pre/post tags', () => {
7
+ const matchText = DUMMY_RESULTS.matches[0].text
8
+ .replace(/\{\{\{/g, '<IA_FTS_MATCH>')
9
+ .replace(/\}\}\}/g, '</IA_FTS_MATCH>');
10
+ const html = renderMatch(matchText, '<IA_FTS_MATCH>', '</IA_FTS_MATCH>');
11
+ expect(html).toContain('<mark>');
12
+ expect(html).toContain('</mark>');
13
+ });
14
+ });
15
+
16
+ describe('marshallSearchResults', () => {
17
+ test('Adds match index', () => {
18
+ const results = deepCopy(DUMMY_RESULTS);
19
+ marshallSearchResults(results, x => x.toString(), '{{{', '}}}');
20
+ expect(results.matches[0]).toHaveProperty('matchIndex', 0);
21
+ expect(results.matches[0].par[0].boxes[0]).toHaveProperty('matchIndex', 0);
22
+ });
23
+
24
+ test('Adds display page number', () => {
25
+ const results = deepCopy(DUMMY_RESULTS);
26
+ marshallSearchResults(results, x => `n${x}`, '{{{', '}}}');
27
+ expect(results.matches[0]).toHaveProperty('displayPageNumber', 'n37');
28
+ });
29
+ });
@@ -1,12 +1,12 @@
1
1
  import sinon from 'sinon';
2
2
  import { afterEventLoop } from '../../utils.js';
3
- import AbstractTTSEngine from '../../../src/plugins/tts/AbstractTTSEngine.js';
4
- import PageChunkIterator from '../../../src/plugins/tts/PageChunkIterator.js';
5
- /** @typedef {import('../../../src/plugins/tts/AbstractTTSEngine.js').TTSEngineOptions} TTSEngineOptions */
3
+ import AbstractTTSEngine from '@/src/plugins/tts/AbstractTTSEngine.js';
4
+ import PageChunkIterator from '@/src/plugins/tts/PageChunkIterator.js';
5
+ /** @typedef {import('@/src/plugins/tts/AbstractTTSEngine.js').TTSEngineOptions} TTSEngineOptions */
6
6
 
7
7
  // Skipping because it's flaky. Fix in #672
8
8
  describe.skip('AbstractTTSEngine', () => {
9
- test('stops playing once done', () => {
9
+ test('stops playing once done', async () => {
10
10
  class DummyEngine extends AbstractTTSEngine {
11
11
  getVoices() { return []; }
12
12
  }
@@ -15,12 +15,12 @@ describe.skip('AbstractTTSEngine', () => {
15
15
  const stopStub = sinon.stub(d, 'stop');
16
16
  expect(stopStub.callCount).toBe(0);
17
17
  d.step();
18
- return afterEventLoop()
19
- .then(() => expect(stopStub.callCount).toBe(1));
18
+ await afterEventLoop();
19
+ expect(stopStub.callCount).toBe(1);
20
20
  });
21
21
  });
22
22
 
23
- for (const dummyVoice of [dummyVoice, dummyVoiceUnderscores]) {
23
+ for (const dummyVoice of [dummyVoiceHyphens, dummyVoiceUnderscores]) {
24
24
  describe(`getBestBookVoice with BCP47 ${dummyVoice == dummyVoiceUnderscores ? '+' : '-'} underscores`, () => {
25
25
  const { getBestBookVoice } = AbstractTTSEngine;
26
26
 
@@ -111,6 +111,26 @@ for (const dummyVoice of [dummyVoice, dummyVoiceUnderscores]) {
111
111
 
112
112
  expect(getBestBookVoice(voices, 'en', ['en-CA', 'en'])).toBe(voices[0]);
113
113
  });
114
+
115
+ test('choose stored language from localStorage', () => {
116
+ const voices = [
117
+ dummyVoice({lang: "en-US", voiceURI: "English US", default: true}),
118
+ dummyVoice({lang: "en-GB", voiceURI: "English GB"}),
119
+ dummyVoice({lang: "en-CA", voiceURI: "English CA"}),
120
+ ];
121
+ class DummyEngine extends AbstractTTSEngine {
122
+ getVoices() { return voices; }
123
+ }
124
+ const ttsEngine = new DummyEngine({...DUMMY_TTS_ENGINE_OPTS, bookLanguage: 'en'});
125
+ // simulates setting default voice on tts startup
126
+ ttsEngine.updateBestVoice();
127
+ // simulates user choosing a voice that matches the bookLanguage
128
+ // voice will be stored in localStorage
129
+ ttsEngine.setVoice(voices[2].voiceURI);
130
+
131
+ // expecting the voice to be selected by getMatchingStoredVoice and returned as best voice
132
+ expect(getBestBookVoice(voices, 'en', [])).toBe(voices[2]);
133
+ });
114
134
  });
115
135
  }
116
136
 
@@ -130,7 +150,7 @@ export const DUMMY_TTS_ENGINE_OPTS = {
130
150
  * @param {SpeechSynthesisVoice}
131
151
  * @return {SpeechSynthesisVoice}
132
152
  **/
133
- function dummyVoice(overrides) {
153
+ function dummyVoiceHyphens(overrides) {
134
154
  return Object.assign({
135
155
  default: false,
136
156
  lang: "en-US",
@@ -147,7 +167,7 @@ function dummyVoice(overrides) {
147
167
  * @return {SpeechSynthesisVoice}
148
168
  **/
149
169
  function dummyVoiceUnderscores(overrides) {
150
- const voice = dummyVoice(overrides);
170
+ const voice = dummyVoiceHyphens(overrides);
151
171
  voice.lang = voice.lang.replace('-', '_');
152
172
  return voice;
153
173
  }