@internetarchive/bookreader 5.0.0-7-multiple-files → 5.0.0-70

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.
Files changed (319) hide show
  1. package/.eslintrc.js +17 -15
  2. package/.github/workflows/node.js.yml +78 -6
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +416 -1365
  6. package/BookReader/BookReader.js +2 -21564
  7. package/BookReader/BookReader.js.LICENSE.txt +20 -20
  8. package/BookReader/BookReader.js.map +1 -1
  9. package/BookReader/ia-bookreader-bundle.js +1509 -0
  10. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +19 -0
  11. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  12. package/BookReader/icons/1up.svg +1 -12
  13. package/BookReader/icons/2up.svg +1 -15
  14. package/BookReader/icons/advance.svg +3 -26
  15. package/BookReader/icons/chevron-right.svg +1 -1
  16. package/BookReader/icons/close-circle-dark.svg +1 -0
  17. package/BookReader/icons/close-circle.svg +1 -1
  18. package/BookReader/icons/fullscreen.svg +1 -17
  19. package/BookReader/icons/fullscreen_exit.svg +1 -17
  20. package/BookReader/icons/hamburger.svg +1 -15
  21. package/BookReader/icons/left-arrow.svg +1 -12
  22. package/BookReader/icons/magnify-minus.svg +1 -16
  23. package/BookReader/icons/magnify-plus.svg +1 -17
  24. package/BookReader/icons/magnify.svg +1 -15
  25. package/BookReader/icons/pause.svg +1 -23
  26. package/BookReader/icons/play.svg +1 -22
  27. package/BookReader/icons/playback-speed.svg +1 -34
  28. package/BookReader/icons/read-aloud.svg +1 -22
  29. package/BookReader/icons/review.svg +3 -22
  30. package/BookReader/icons/thumbnails.svg +1 -17
  31. package/BookReader/icons/voice.svg +1 -0
  32. package/BookReader/icons/volume-full.svg +1 -22
  33. package/BookReader/images/BRicons.svg +5 -94
  34. package/BookReader/images/books_graphic.svg +1 -177
  35. package/BookReader/images/icon_book.svg +1 -12
  36. package/BookReader/images/icon_bookmark.svg +1 -12
  37. package/BookReader/images/icon_gear.svg +1 -14
  38. package/BookReader/images/icon_hamburger.svg +1 -20
  39. package/BookReader/images/icon_home.svg +1 -21
  40. package/BookReader/images/icon_info.svg +1 -11
  41. package/BookReader/images/icon_one_page.svg +1 -8
  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 -15
  45. package/BookReader/images/icon_search_button.svg +1 -8
  46. package/BookReader/images/icon_share.svg +1 -9
  47. package/BookReader/images/icon_skip-ahead.svg +1 -6
  48. package/BookReader/images/icon_skip-back.svg +2 -13
  49. package/BookReader/images/icon_speaker.svg +1 -18
  50. package/BookReader/images/icon_speaker_open.svg +1 -10
  51. package/BookReader/images/icon_thumbnails.svg +1 -12
  52. package/BookReader/images/icon_toc.svg +1 -5
  53. package/BookReader/images/icon_two_pages.svg +1 -9
  54. package/BookReader/images/marker_chap-off.svg +1 -11
  55. package/BookReader/images/marker_chap-on.svg +1 -11
  56. package/BookReader/images/marker_srch-on.svg +1 -11
  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 -172
  60. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  61. package/BookReader/plugins/plugin.autoplay.js +1 -165
  62. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  63. package/BookReader/plugins/plugin.chapters.js +22 -301
  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 -74
  67. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  68. package/BookReader/plugins/plugin.resume.js +1 -368
  69. package/BookReader/plugins/plugin.resume.js.map +1 -1
  70. package/BookReader/plugins/plugin.search.js +2 -1420
  71. package/BookReader/plugins/plugin.search.js.LICENSE.txt +1 -0
  72. package/BookReader/plugins/plugin.search.js.map +1 -1
  73. package/BookReader/plugins/plugin.text_selection.js +2 -1080
  74. package/BookReader/plugins/plugin.text_selection.js.LICENSE.txt +1 -0
  75. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  76. package/BookReader/plugins/plugin.tts.js +2 -9193
  77. package/BookReader/plugins/plugin.tts.js.LICENSE.txt +2 -0
  78. package/BookReader/plugins/plugin.tts.js.map +1 -1
  79. package/BookReader/plugins/plugin.url.js +1 -269
  80. package/BookReader/plugins/plugin.url.js.map +1 -1
  81. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -379
  82. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  83. package/BookReader/webcomponents-bundle.js +3 -0
  84. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  85. package/BookReader/webcomponents-bundle.js.map +1 -0
  86. package/BookReaderDemo/BookReaderDemo.css +16 -19
  87. package/BookReaderDemo/BookReaderJSAdvanced.js +0 -3
  88. package/BookReaderDemo/BookReaderJSAutoplay.js +4 -1
  89. package/BookReaderDemo/BookReaderJSSimple.js +1 -0
  90. package/BookReaderDemo/IADemoBr.js +147 -0
  91. package/BookReaderDemo/demo-advanced.html +2 -2
  92. package/BookReaderDemo/demo-autoplay.html +2 -3
  93. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  94. package/BookReaderDemo/demo-fullscreen-mobile.html +3 -5
  95. package/BookReaderDemo/demo-fullscreen.html +2 -4
  96. package/BookReaderDemo/demo-iiif.html +2 -1
  97. package/BookReaderDemo/demo-iiif.js +0 -1
  98. package/BookReaderDemo/demo-internetarchive.html +213 -17
  99. package/BookReaderDemo/demo-multiple.html +2 -1
  100. package/BookReaderDemo/demo-preview-pages.html +2 -1
  101. package/BookReaderDemo/demo-simple.html +2 -1
  102. package/BookReaderDemo/demo-vendor-fullscreen.html +2 -4
  103. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  104. package/BookReaderDemo/immersion-1up.html +2 -2
  105. package/BookReaderDemo/immersion-mode.html +2 -4
  106. package/BookReaderDemo/toggle_controls.html +3 -2
  107. package/BookReaderDemo/view_mode.html +2 -1
  108. package/BookReaderDemo/viewmode-cycle.html +2 -3
  109. package/CHANGELOG.md +289 -0
  110. package/README.md +14 -1
  111. package/babel.config.js +20 -0
  112. package/codecov.yml +6 -0
  113. package/index.html +4 -1
  114. package/jsconfig.json +19 -0
  115. package/netlify.toml +9 -0
  116. package/package.json +71 -60
  117. package/renovate.json +52 -0
  118. package/scripts/preversion.js +4 -1
  119. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  120. package/src/BookNavigator/assets/button-base.js +9 -2
  121. package/src/BookNavigator/assets/ia-logo.js +17 -0
  122. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  123. package/src/BookNavigator/assets/icon_close.js +1 -1
  124. package/src/BookNavigator/assets/icon_sort_asc.js +5 -0
  125. package/src/BookNavigator/assets/{icon_sort_ascending.js → icon_sort_desc.js} +2 -2
  126. package/src/BookNavigator/assets/icon_sort_neutral.js +5 -0
  127. package/src/BookNavigator/assets/icon_volumes.js +11 -0
  128. package/src/BookNavigator/book-navigator.js +586 -0
  129. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  130. package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
  131. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  132. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
  133. package/src/BookNavigator/bookmarks/bookmarks-provider.js +27 -17
  134. package/src/BookNavigator/bookmarks/ia-bookmarks.js +116 -67
  135. package/src/BookNavigator/delete-modal-actions.js +1 -1
  136. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  137. package/src/BookNavigator/downloads/downloads.js +41 -25
  138. package/src/BookNavigator/search/search-provider.js +80 -28
  139. package/src/BookNavigator/search/search-results.js +28 -25
  140. package/src/BookNavigator/sharing.js +27 -0
  141. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -10
  142. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
  143. package/src/BookNavigator/volumes/volumes-provider.js +88 -53
  144. package/src/BookNavigator/volumes/volumes.js +41 -14
  145. package/src/BookReader/BookModel.js +64 -34
  146. package/src/BookReader/DragScrollable.js +233 -0
  147. package/src/BookReader/Mode1Up.js +56 -351
  148. package/src/BookReader/Mode1UpLit.js +388 -0
  149. package/src/BookReader/Mode2Up.js +73 -1318
  150. package/src/BookReader/Mode2UpLit.js +776 -0
  151. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  152. package/src/BookReader/ModeSmoothZoom.js +312 -0
  153. package/src/BookReader/ModeThumb.js +18 -12
  154. package/src/BookReader/Navbar/Navbar.js +12 -38
  155. package/src/BookReader/PageContainer.js +81 -6
  156. package/src/BookReader/ReduceSet.js +1 -1
  157. package/src/BookReader/Toolbar/Toolbar.js +10 -37
  158. package/src/BookReader/events.js +2 -3
  159. package/src/BookReader/options.js +24 -2
  160. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  161. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  162. package/src/BookReader/utils/SelectionObserver.js +43 -0
  163. package/src/BookReader/utils.js +118 -13
  164. package/src/BookReader.js +446 -1062
  165. package/src/assets/icons/close-circle-dark.svg +1 -0
  166. package/src/assets/icons/magnify-minus.svg +3 -7
  167. package/src/assets/icons/magnify-plus.svg +3 -7
  168. package/src/assets/icons/voice.svg +1 -0
  169. package/src/css/BookReader.scss +1 -17
  170. package/src/css/_BRBookmarks.scss +1 -1
  171. package/src/css/_BRComponent.scss +1 -1
  172. package/src/css/_BRmain.scss +33 -24
  173. package/src/css/_BRnav.scss +11 -38
  174. package/src/css/_BRpages.scss +149 -40
  175. package/src/css/_BRsearch.scss +67 -226
  176. package/src/css/_TextSelection.scss +87 -27
  177. package/src/css/_colorbox.scss +2 -2
  178. package/src/css/_controls.scss +20 -7
  179. package/src/css/_icons.scss +7 -1
  180. package/src/ia-bookreader/ia-bookreader.js +224 -0
  181. package/src/plugins/plugin.archive_analytics.js +3 -3
  182. package/src/plugins/plugin.autoplay.js +5 -11
  183. package/src/plugins/plugin.chapters.js +211 -186
  184. package/src/plugins/plugin.resume.js +3 -3
  185. package/src/plugins/plugin.text_selection.js +464 -134
  186. package/src/plugins/plugin.vendor-fullscreen.js +4 -4
  187. package/src/plugins/search/plugin.search.js +175 -120
  188. package/src/plugins/search/utils.js +43 -0
  189. package/src/plugins/search/view.js +64 -202
  190. package/src/plugins/tts/AbstractTTSEngine.js +68 -40
  191. package/src/plugins/tts/FestivalTTSEngine.js +13 -14
  192. package/src/plugins/tts/PageChunk.js +15 -21
  193. package/src/plugins/tts/PageChunkIterator.js +8 -12
  194. package/src/plugins/tts/WebTTSEngine.js +87 -71
  195. package/src/plugins/tts/plugin.tts.js +95 -126
  196. package/src/plugins/tts/utils.js +0 -25
  197. package/src/plugins/url/UrlPlugin.js +191 -0
  198. package/src/plugins/{plugin.url.js → url/plugin.url.js} +45 -16
  199. package/src/util/browserSniffing.js +22 -0
  200. package/src/util/docCookies.js +21 -2
  201. package/tests/e2e/README.md +37 -0
  202. package/tests/e2e/autoplay.test.js +2 -2
  203. package/tests/e2e/base.test.js +8 -16
  204. package/tests/e2e/helpers/base.js +53 -48
  205. package/tests/e2e/helpers/debug.js +1 -1
  206. package/tests/e2e/helpers/params.js +17 -0
  207. package/tests/e2e/helpers/rightToLeft.js +8 -14
  208. package/tests/e2e/helpers/search.js +73 -0
  209. package/tests/e2e/models/Navigation.js +20 -37
  210. package/tests/e2e/rightToLeft.test.js +4 -5
  211. package/tests/e2e/viewmode.test.js +40 -33
  212. package/tests/jest/BookNavigator/book-navigator.test.js +658 -0
  213. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  214. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  215. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  216. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  217. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  218. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  219. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  220. package/tests/{karma/BookNavigator → jest/BookNavigator/search}/search-results.test.js +109 -60
  221. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  222. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  223. package/tests/jest/BookNavigator/volumes/volumes-provider.test.js +184 -0
  224. package/tests/jest/BookNavigator/volumes/volumes.test.js +97 -0
  225. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +74 -14
  226. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
  227. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  228. package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
  229. package/tests/jest/BookReader/Mode2Up.test.js +98 -0
  230. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  231. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  232. package/tests/jest/BookReader/ModeSmoothZoom.test.js +218 -0
  233. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  234. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +10 -10
  235. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +88 -6
  236. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  237. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
  238. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  239. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  240. package/tests/jest/BookReader/utils/SelectionObserver.test.js +43 -0
  241. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  242. package/tests/jest/BookReader/utils.test.js +229 -0
  243. package/tests/jest/BookReader.keyboard.test.js +190 -0
  244. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
  245. package/tests/{BookReader.test.js → jest/BookReader.test.js} +26 -37
  246. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
  247. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +4 -4
  248. package/tests/jest/plugins/plugin.chapters.test.js +145 -0
  249. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
  250. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
  251. package/tests/jest/plugins/plugin.text_selection.test.js +317 -0
  252. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  253. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +26 -47
  254. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +39 -6
  255. package/tests/jest/plugins/search/utils.js +25 -0
  256. package/tests/jest/plugins/search/utils.test.js +29 -0
  257. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +29 -9
  258. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  259. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  260. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  261. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
  262. package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
  263. package/tests/jest/plugins/url/UrlPlugin.test.js +198 -0
  264. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +53 -14
  265. package/tests/jest/setup.js +3 -0
  266. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  267. package/tests/jest/util/docCookies.test.js +24 -0
  268. package/tests/{util → jest/util}/strings.test.js +1 -1
  269. package/tests/{utils.js → jest/utils.js} +38 -0
  270. package/webpack.config.js +11 -6
  271. package/.babelrc +0 -12
  272. package/.dependabot/config.yml +0 -6
  273. package/.testcaferc.json +0 -5
  274. package/BookReader/bookreader-component-bundle.js +0 -14311
  275. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  276. package/BookReader/bookreader-component-bundle.js.map +0 -1
  277. package/BookReader/icons/sort-ascending.svg +0 -1
  278. package/BookReader/icons/sort-descending.svg +0 -1
  279. package/BookReader/jquery-1.10.1.js +0 -108
  280. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  281. package/BookReader/plugins/plugin.menu_toggle.js +0 -369
  282. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  283. package/BookReader/plugins/plugin.mobile_nav.js +0 -335
  284. package/BookReader/plugins/plugin.mobile_nav.js.map +0 -1
  285. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  286. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  287. package/karma.conf.js +0 -23
  288. package/src/BookNavigator/BookModel.js +0 -14
  289. package/src/BookNavigator/BookNavigator.js +0 -452
  290. package/src/BookNavigator/assets/book-loader.js +0 -27
  291. package/src/BookNavigator/assets/icon_sort_descending.js +0 -5
  292. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  293. package/src/BookNavigator/search/a-search-result.js +0 -55
  294. package/src/BookReader/DebugConsole.js +0 -54
  295. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  296. package/src/ItemNavigator/ItemNavigator.js +0 -372
  297. package/src/ItemNavigator/providers/sharing.js +0 -29
  298. package/src/assets/icons/sort-ascending.svg +0 -1
  299. package/src/assets/icons/sort-descending.svg +0 -1
  300. package/src/css/_MobileNav.scss +0 -194
  301. package/src/dragscrollable-br.js +0 -261
  302. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  303. package/src/plugins/plugin.mobile_nav.js +0 -287
  304. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  305. package/tests/BookReader/DebugConsole.test.js +0 -25
  306. package/tests/BookReader/Mode1Up.test.js +0 -164
  307. package/tests/BookReader/Mode2Up.test.js +0 -247
  308. package/tests/BookReader/utils.test.js +0 -109
  309. package/tests/e2e/helpers/desktopSearch.js +0 -72
  310. package/tests/e2e/helpers/mobileSearch.js +0 -85
  311. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  312. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  313. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  314. package/tests/karma/BookNavigator/volumes.test.js +0 -101
  315. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  316. package/tests/plugins/plugin.chapters.test.js +0 -130
  317. package/tests/plugins/plugin.mobile_nav.test.js +0 -66
  318. package/tests/plugins/plugin.text_selection.test.js +0 -203
  319. package/tests/util/docCookies.test.js +0 -15
@@ -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
  }
@@ -1,10 +1,10 @@
1
- import FestivalTTSEngine from '../../../src/plugins/tts/FestivalTTSEngine.js';
1
+ import FestivalTTSEngine from '@/src/plugins/tts/FestivalTTSEngine.js';
2
2
  import sinon from 'sinon';
3
3
  import { afterEventLoop } from '../../utils.js';
4
4
  import { DUMMY_TTS_ENGINE_OPTS } from './AbstractTTSEngine.test.js';
5
- import PageChunk from '../../../src/plugins/tts/PageChunk.js';
6
- import PageChunkIterator from '../../../src/plugins/tts/PageChunkIterator.js';
7
- /** @typedef {import('../../../src/plugins/tts/AbstractTTSEngine.js').TTSEngineOptions} TTSEngineOptions */
5
+ import PageChunk from '@/src/plugins/tts/PageChunk.js';
6
+ import PageChunkIterator from '@/src/plugins/tts/PageChunkIterator.js';
7
+ /** @typedef {import('@/src/plugins/tts/AbstractTTSEngine.js').TTSEngineOptions} TTSEngineOptions */
8
8
 
9
9
  describe('iOSCaptureUserIntentHack', () => {
10
10
  test('synchronously calls createSound/play to capture user intent', () => {
@@ -1,4 +1,4 @@
1
- import PageChunk from '../../../src/plugins/tts/PageChunk.js';
1
+ import PageChunk from '@/src/plugins/tts/PageChunk.js';
2
2
 
3
3
  describe('_fixChunkRects', () => {
4
4
  const { _fixChunkRects } = PageChunk;
@@ -1,6 +1,6 @@
1
1
  import sinon from 'sinon';
2
- import PageChunkIterator from "../../../src/plugins/tts/PageChunkIterator";
3
- import PageChunk from '../../../src/plugins/tts/PageChunk';
2
+ import PageChunkIterator from "@/src/plugins/tts/PageChunkIterator";
3
+ import PageChunk from '@/src/plugins/tts/PageChunk';
4
4
 
5
5
  describe('Buffers pages', () => {
6
6
  test('Does not error if no room for reverse buffer', async () => {
@@ -109,7 +109,7 @@ describe('Iterates pages', () => {
109
109
  expect(iterator._cursor.chunk).toBe(0);
110
110
 
111
111
  for (let i = 2; i >= 0; i--) {
112
- await iterator.decrement()
112
+ await iterator.decrement();
113
113
  expect(iterator._cursor.page).toBe(49);
114
114
  expect(iterator._cursor.chunk).toBe(i);
115
115
  }
@@ -1,5 +1,5 @@
1
1
  import sinon from 'sinon';
2
- import { WebTTSSound } from '../../../src/plugins/tts/WebTTSEngine.js';
2
+ import WebTTSEngine, { WebTTSSound } from '@/src/plugins/tts/WebTTSEngine.js';
3
3
  import { afterEventLoop, eventTargetMixin } from '../../utils.js';
4
4
 
5
5
  beforeEach(() => {
@@ -8,6 +8,7 @@ beforeEach(() => {
8
8
  speak: sinon.stub(),
9
9
  pause: sinon.stub(),
10
10
  resume: sinon.stub(),
11
+
11
12
  ...eventTargetMixin(),
12
13
  };
13
14
  window.SpeechSynthesisUtterance = function (text) {
@@ -21,6 +22,51 @@ afterEach(() => {
21
22
  delete window.SpeechSynthesisUtterance;
22
23
  });
23
24
 
25
+ describe('WebTTSEngine', () => {
26
+ test('getVoices should include default voice when no actual default', () => {
27
+ // iOS devices set all the voices to default -_-
28
+ speechSynthesis.getVoices = () => [
29
+ {
30
+ default: true,
31
+ lang: "ar-001",
32
+ localService: true,
33
+ name: "Majed",
34
+ voiceURI: "com.apple.voice.compact.ar-001.Maged",
35
+ },
36
+ {
37
+ default: true,
38
+ lang: "bg-BG",
39
+ localService: true,
40
+ name: "Daria",
41
+ voiceURI: "com.apple.voice.compact.bg-BG.Daria",
42
+ }
43
+ ];
44
+ const voices = WebTTSEngine.prototype.getVoices();
45
+ expect(voices.length).toBe(3);
46
+ expect(voices[0].voiceURI).toBe('bookreader.SystemDefault');
47
+ });
48
+
49
+ test('getVoices should not include default voice when there is a default', () => {
50
+ speechSynthesis.getVoices = () => [
51
+ {
52
+ default: true,
53
+ lang: "ar-001",
54
+ localService: true,
55
+ name: "Majed",
56
+ voiceURI: "com.apple.voice.compact.ar-001.Maged",
57
+ },
58
+ {
59
+ default: false,
60
+ lang: "bg-BG",
61
+ localService: true,
62
+ name: "Daria",
63
+ voiceURI: "com.apple.voice.compact.bg-BG.Daria",
64
+ }
65
+ ];
66
+ const voices = WebTTSEngine.prototype.getVoices();
67
+ expect(voices.length).toBe(2);
68
+ });
69
+ });
24
70
 
25
71
  describe('WebTTSSound', () => {
26
72
  describe('setPlaybackRate', () => {
@@ -1,38 +1,4 @@
1
- import sinon from 'sinon';
2
- import { afterEventLoop, eventTargetMixin } from '../../utils.js';
3
- import * as utils from '../../../src/plugins/tts/utils.js';
4
-
5
- describe('promisifyEvent', () => {
6
- const { promisifyEvent } = utils;
7
-
8
- test('Resolves once event fires', async () => {
9
- const fakeTarget = eventTargetMixin();
10
- const resolveSpy = sinon.spy();
11
- promisifyEvent(fakeTarget, 'pause').then(resolveSpy);
12
-
13
- await afterEventLoop()
14
- expect(resolveSpy.callCount).toBe(0);
15
- fakeTarget.dispatchEvent('pause', {});
16
- await afterEventLoop();
17
- expect(resolveSpy.callCount).toBe(1);
18
- });
19
-
20
- test('Only resolves once', async () => {
21
- const fakeTarget = eventTargetMixin();
22
- const resolveSpy = sinon.spy();
23
- promisifyEvent(fakeTarget, 'pause').then(resolveSpy);
24
-
25
- await afterEventLoop()
26
- expect(resolveSpy.callCount).toBe(0);
27
- fakeTarget.dispatchEvent('pause', {});
28
- fakeTarget.dispatchEvent('pause', {});
29
- fakeTarget.dispatchEvent('pause', {});
30
- fakeTarget.dispatchEvent('pause', {});
31
-
32
- await afterEventLoop();
33
- expect(resolveSpy.callCount).toBe(1);
34
- });
35
- });
1
+ import * as utils from '@/src/plugins/tts/utils.js';
36
2
 
37
3
  describe('approximateWordCount', () => {
38
4
  const { approximateWordCount } = utils;
@@ -66,31 +32,6 @@ describe('approximateWordCount', () => {
66
32
  });
67
33
  });
68
34
 
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
35
  describe('toISO6391', () => {
95
36
  const { toISO6391 } = utils;
96
37
 
@@ -0,0 +1,198 @@
1
+ import { UrlPlugin } from '@/src/plugins/url/UrlPlugin';
2
+
3
+ afterEach(() => {
4
+ jest.clearAllMocks();
5
+ });
6
+
7
+ describe('UrlPlugin tests', () => {
8
+ const urlPlugin = new UrlPlugin();
9
+
10
+ describe('urlStateToUrlString tests', () => {
11
+ test('urlStateToUrlString with known states in schema', () => {
12
+ const urlState = { page: 'n7', mode: '1up', search: 'foo' };
13
+ const urlStateWithQueries = { page: 'n7', mode: '1up', q: 'hello', view: 'theater', sort: 'title_asc' };
14
+
15
+ const expectedUrlFromState = 'page/n7/mode/1up?q=foo';
16
+ const expectedUrlFromStateWithQueries = 'page/n7/mode/1up?q=hello&view=theater&sort=title_asc';
17
+
18
+ expect(urlPlugin.urlStateToUrlString(urlState)).toBe(expectedUrlFromState);
19
+ expect(urlPlugin.urlStateToUrlString(urlStateWithQueries)).toBe(expectedUrlFromStateWithQueries);
20
+ });
21
+
22
+ test('encodes page number', () => {
23
+ expect(urlPlugin.urlStateToUrlString({ page: '12/46' })).toBe(`page/12%2F46`);
24
+ });
25
+
26
+ test('urlStateToUrlString with unknown states in schema', () => {
27
+ const urlState = { page: 'n7', mode: '1up' };
28
+ const urlStateWithQueries = { page: 'n7', mode: '1up', q: 'hello', viewer: 'theater', sortBy: 'title_asc' };
29
+
30
+ const expectedUrlFromState = 'page/n7/mode/1up';
31
+ const expectedUrlFromStateWithQueries = 'page/n7/mode/1up?q=hello&viewer=theater&sortBy=title_asc';
32
+
33
+ expect(urlPlugin.urlStateToUrlString(urlState)).toBe(expectedUrlFromState);
34
+ expect(urlPlugin.urlStateToUrlString(urlStateWithQueries)).toBe(expectedUrlFromStateWithQueries);
35
+ });
36
+
37
+ test('urlStateToUrlString with boolean value', () => {
38
+ const urlState = { page: 'n7', mode: '1up', search: 'foo', view: 'theater', wrapper: 'false' };
39
+ const expectedUrlFromState = 'page/n7/mode/1up?q=foo&view=theater&wrapper=false';
40
+
41
+ expect(urlPlugin.urlStateToUrlString(urlState)).toBe(expectedUrlFromState);
42
+ });
43
+ });
44
+
45
+ describe('urlStringToUrlState tests', () => {
46
+ test('urlStringToUrlState without query string', () => {
47
+ const url = '/page/n7/mode/2up';
48
+ const url1 = '/page/n7/mode/1up';
49
+
50
+ expect(urlPlugin.urlStringToUrlState(url)).toEqual({page: 'n7', mode: '2up'});
51
+ expect(urlPlugin.urlStringToUrlState(url1)).toEqual({page: 'n7', mode: '1up'});
52
+ });
53
+
54
+ test('decodes page number', () => {
55
+ expect(urlPlugin.urlStringToUrlState('/page/12%2F46')).toStrictEqual({ page: '12/46' });
56
+ });
57
+
58
+ test('urlStringToUrlState with deprecated_for', () => {
59
+ const url = '/page/n7/mode/2up/search/hello';
60
+
61
+ expect(urlPlugin.urlStringToUrlState(url)).toEqual({page: 'n7', mode: '2up', q: 'hello'});
62
+ });
63
+
64
+ test('urlStringToUrlState with query string', () => {
65
+ const url = '/page/n7/mode/2up/search/hello?view=theather&foo=bar&sort=title_asc';
66
+ const url1 = '/mode/2up?ref=ol&ui=embed&wrapper=false&view=theater';
67
+
68
+ expect(urlPlugin.urlStringToUrlState(url)).toEqual(
69
+ {page: 'n7', mode: '2up', q: 'hello', view: 'theather', foo: 'bar', sort: 'title_asc'}
70
+ );
71
+ expect(urlPlugin.urlStringToUrlState(url1)).toEqual(
72
+ {mode: '2up', ref: 'ol', ui: 'embed', wrapper: 'false', view: 'theater'}
73
+ );
74
+ });
75
+
76
+ test('urlStringToUrlState compare search and ?q', () => {
77
+ const url = '/page/n7/mode/2up/search/hello';
78
+ urlPlugin.urlState = { q: 'hello' };
79
+
80
+ expect(urlPlugin.urlStringToUrlState(url)).toEqual({page: 'n7', mode: '2up', q: 'hello'});
81
+ });
82
+ });
83
+
84
+ describe('url plugin helper functions', () => {
85
+ test('setUrlParam', () => {
86
+ urlPlugin.urlState = {};
87
+ urlPlugin.setUrlParam('page', '20');
88
+ urlPlugin.setUrlParam('mode', '2up');
89
+
90
+ expect(urlPlugin.urlState).toEqual({page: '20', mode: '2up'});
91
+ });
92
+
93
+ test('removeUrlParam', () => {
94
+ urlPlugin.setUrlParam('page', '20');
95
+ urlPlugin.setUrlParam('mode', '2up');
96
+ urlPlugin.removeUrlParam('mode');
97
+
98
+ expect(urlPlugin.urlState).toEqual({page: '20'});
99
+ });
100
+
101
+ test('getUrlParam', () => {
102
+ urlPlugin.setUrlParam('page', '20');
103
+ urlPlugin.setUrlParam('mode', '2up');
104
+ expect(urlPlugin.getUrlParam('page')).toEqual('20');
105
+ expect(urlPlugin.getUrlParam('mode')).toEqual('2up');
106
+ });
107
+ });
108
+
109
+ describe('pullFromAddressBar and pushToAddressBar - hash mode', () => {
110
+ test('url first load - empty state', () => {
111
+ urlPlugin.urlState = {};
112
+ urlPlugin.urlMode = 'hash';
113
+
114
+ urlPlugin.pullFromAddressBar({ pathname: '', search: '', hash: '#' });
115
+ expect(urlPlugin.urlState).toEqual({});
116
+
117
+ urlPlugin.pushToAddressBar();
118
+ expect(window.location.hash).toEqual('');
119
+ });
120
+
121
+ test('url without mode state value - use default', () => {
122
+ urlPlugin.urlState = {};
123
+ urlPlugin.urlMode = 'hash';
124
+
125
+ urlPlugin.pullFromAddressBar({ pathname: '', search: '', hash: '#page/12' });
126
+ expect(urlPlugin.urlState).toEqual({page: '12'});
127
+
128
+ urlPlugin.pushToAddressBar();
129
+ expect(window.location.hash).toEqual('#page/12');
130
+ });
131
+
132
+ test('url with query param', () => {
133
+ urlPlugin.urlState = {};
134
+ urlPlugin.urlMode = 'hash';
135
+
136
+ urlPlugin.pullFromAddressBar({ pathname: '', search: '', hash: '#page/12?q=hello&view=theater' });
137
+ expect(urlPlugin.urlState).toEqual({page: '12', q: 'hello', view: 'theater'});
138
+
139
+ urlPlugin.pushToAddressBar();
140
+ expect(window.location.hash).toEqual('#page/12?q=hello&view=theater');
141
+ });
142
+ });
143
+
144
+ describe('pullFromAddressBar and pushToAddressBar - history mode', () => {
145
+ test('url first load - empty state', () => {
146
+ urlPlugin.urlState = {};
147
+ urlPlugin.urlHistoryBasePath = '/details/foo';
148
+ urlPlugin.urlMode = 'history';
149
+
150
+ urlPlugin.pullFromAddressBar({ pathname: '', search: '', hash: '' });
151
+ expect(urlPlugin.urlState).toEqual({});
152
+
153
+ urlPlugin.pushToAddressBar();
154
+ expect(window.location.pathname).toEqual('/details/foo');
155
+ });
156
+
157
+ test('url without mode state value', () => {
158
+ urlPlugin.urlState = {};
159
+ urlPlugin.urlHistoryBasePath = '/details/foo/';
160
+ urlPlugin.urlMode = 'history';
161
+
162
+ urlPlugin.pullFromAddressBar({ pathname: '/details/foo/page/12', search: '', hash: '' });
163
+ expect(urlPlugin.urlState).toEqual({page: '12'});
164
+
165
+ urlPlugin.pushToAddressBar();
166
+ expect(window.location.pathname).toEqual('/details/foo/page/12');
167
+ });
168
+
169
+ test('url with query param', () => {
170
+ urlPlugin.urlState = {};
171
+ urlPlugin.urlHistoryBasePath = '/details/foo/';
172
+ urlPlugin.urlMode = 'history';
173
+
174
+ urlPlugin.pullFromAddressBar({ pathname: '/details/foo/page/12', search: '?q=hello&view=theater', hash: '' });
175
+ expect(urlPlugin.urlState).toEqual({page: '12', q: 'hello', view: 'theater'});
176
+
177
+ urlPlugin.pushToAddressBar();
178
+ const locationUrl = `${window.location.pathname}${window.location.search}`;
179
+ expect(locationUrl).toEqual('/details/foo/page/12?q=hello&view=theater');
180
+ });
181
+
182
+ test('strips leading slash of incoming path name for no double slash', () => {
183
+ const urlPlugin = new UrlPlugin();
184
+ urlPlugin.urlMode = 'history';
185
+
186
+ urlPlugin.urlHistoryBasePath = '/details/SubBookTest/book1/GPORFP/';
187
+ urlPlugin.urlState = {
188
+ "mode": "1up",
189
+ };
190
+
191
+ urlPlugin.setUrlParam('sort', 'title_asc');
192
+ urlPlugin.setUrlParam('mode', 'thumb');
193
+
194
+ expect(window.location.href).toEqual('http://localhost/details/SubBookTest/book1/GPORFP/mode/thumb?sort=title_asc');
195
+ });
196
+ });
197
+
198
+ });