@internetarchive/bookreader 5.0.0-24 → 5.0.0-24-sortingstate-3

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 (79) hide show
  1. package/.nvmrc +1 -0
  2. package/BookReader/BookReader.js +32145 -2
  3. package/BookReader/BookReader.js.map +1 -1
  4. package/BookReader/bookreader-component-bundle.js +10911 -941
  5. package/BookReader/bookreader-component-bundle.js.map +1 -1
  6. package/BookReader/icons/1up.svg +12 -1
  7. package/BookReader/icons/2up.svg +15 -1
  8. package/BookReader/icons/advance.svg +26 -3
  9. package/BookReader/icons/chevron-right.svg +1 -1
  10. package/BookReader/icons/close-circle-dark.svg +1 -1
  11. package/BookReader/icons/close-circle.svg +1 -1
  12. package/BookReader/icons/fullscreen.svg +17 -1
  13. package/BookReader/icons/fullscreen_exit.svg +17 -1
  14. package/BookReader/icons/hamburger.svg +15 -1
  15. package/BookReader/icons/left-arrow.svg +12 -1
  16. package/BookReader/icons/magnify-minus.svg +16 -1
  17. package/BookReader/icons/magnify-plus.svg +17 -1
  18. package/BookReader/icons/magnify.svg +15 -1
  19. package/BookReader/icons/pause.svg +23 -1
  20. package/BookReader/icons/play.svg +22 -1
  21. package/BookReader/icons/playback-speed.svg +34 -1
  22. package/BookReader/icons/read-aloud.svg +22 -1
  23. package/BookReader/icons/review.svg +22 -3
  24. package/BookReader/icons/thumbnails.svg +17 -1
  25. package/BookReader/icons/voice.svg +1 -1
  26. package/BookReader/icons/volume-full.svg +22 -1
  27. package/BookReader/images/BRicons.svg +94 -5
  28. package/BookReader/images/books_graphic.svg +177 -1
  29. package/BookReader/images/icon_book.svg +12 -1
  30. package/BookReader/images/icon_bookmark.svg +12 -1
  31. package/BookReader/images/icon_gear.svg +14 -1
  32. package/BookReader/images/icon_hamburger.svg +20 -1
  33. package/BookReader/images/icon_home.svg +21 -1
  34. package/BookReader/images/icon_info.svg +11 -1
  35. package/BookReader/images/icon_one_page.svg +8 -1
  36. package/BookReader/images/icon_pause.svg +1 -1
  37. package/BookReader/images/icon_play.svg +1 -1
  38. package/BookReader/images/icon_playback-rate.svg +15 -1
  39. package/BookReader/images/icon_search_button.svg +8 -1
  40. package/BookReader/images/icon_share.svg +9 -1
  41. package/BookReader/images/icon_skip-ahead.svg +6 -1
  42. package/BookReader/images/icon_skip-back.svg +13 -2
  43. package/BookReader/images/icon_speaker.svg +18 -1
  44. package/BookReader/images/icon_speaker_open.svg +10 -1
  45. package/BookReader/images/icon_thumbnails.svg +12 -1
  46. package/BookReader/images/icon_toc.svg +5 -1
  47. package/BookReader/images/icon_two_pages.svg +9 -1
  48. package/BookReader/images/marker_chap-off.svg +11 -1
  49. package/BookReader/images/marker_chap-on.svg +11 -1
  50. package/BookReader/images/marker_srch-on.svg +11 -1
  51. package/BookReader/jquery-1.10.1.js +108 -2
  52. package/BookReader/plugins/plugin.archive_analytics.js +170 -1
  53. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  54. package/BookReader/plugins/plugin.autoplay.js +163 -1
  55. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  56. package/BookReader/plugins/plugin.chapters.js +333 -1
  57. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  58. package/BookReader/plugins/plugin.iframe.js +72 -1
  59. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  60. package/BookReader/plugins/plugin.mobile_nav.js +332 -1
  61. package/BookReader/plugins/plugin.mobile_nav.js.map +1 -1
  62. package/BookReader/plugins/plugin.resume.js +241 -1
  63. package/BookReader/plugins/plugin.resume.js.map +1 -1
  64. package/BookReader/plugins/plugin.search.js +1263 -1
  65. package/BookReader/plugins/plugin.search.js.map +1 -1
  66. package/BookReader/plugins/plugin.text_selection.js +839 -1
  67. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  68. package/BookReader/plugins/plugin.tts.js +9114 -2
  69. package/BookReader/plugins/plugin.tts.js.map +1 -1
  70. package/BookReader/plugins/plugin.url.js +764 -1
  71. package/BookReader/plugins/plugin.url.js.map +1 -1
  72. package/BookReader/plugins/plugin.vendor-fullscreen.js +326 -1
  73. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  74. package/BookReader/webcomponents-bundle.js +411 -2
  75. package/BookReader/webcomponents-bundle.js.map +1 -1
  76. package/package.json +1 -1
  77. package/src/BookNavigator/volumes/volumes-provider.js +40 -9
  78. package/src/plugins/plugin.url.js +214 -2
  79. package/tests/jest/plugins/plugin.url.test.js +151 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/bookreader",
3
- "version": "5.0.0-24",
3
+ "version": "5.0.0-24-sortingstate-3",
4
4
  "description": "The Internet Archive BookReader.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -7,8 +7,16 @@ import volumesIcon from '../assets/icon_volumes.js';
7
7
 
8
8
  import './volumes.js';
9
9
 
10
+ const sortType = {
11
+ title_asc: 'title_asc',
12
+ title_desc: 'title_desc',
13
+ default: 'default'
14
+ };
10
15
  export default class VolumesProvider {
11
16
 
17
+ /**
18
+ * @param {import('../../BookReader').default} bookreader
19
+ */
12
20
  constructor(baseHost, bookreader, optionChange) {
13
21
  this.optionChange = optionChange;
14
22
  this.component = document.createElement("viewable-files");
@@ -17,6 +25,9 @@ export default class VolumesProvider {
17
25
  this.viewableFiles = Object.keys(files).map(item => files[item]);
18
26
  this.volumeCount = Object.keys(files).length;
19
27
 
28
+ /** @type {import('../../BookReader').default} */
29
+ this.bookreader = bookreader;
30
+
20
31
  this.component.subPrefix = bookreader.options.subPrefix || "";
21
32
  this.component.hostUrl = baseHost;
22
33
  this.component.viewableFiles = this.viewableFiles;
@@ -25,20 +36,28 @@ export default class VolumesProvider {
25
36
  this.label = `Viewable files (${this.volumeCount})`;
26
37
  this.icon = html`${volumesIcon}`;
27
38
 
28
- this.sortOrderBy = "orig_sort";
29
- this.sortVolumes("orig_sort");
39
+ // get sort state from query param
40
+ this.bookreader.urlPlugin.pullFromAddressBar();
41
+ const urlSortValue = this.bookreader.urlPlugin.getUrlParam('sort');
42
+ console.log('urlSortValue: ', urlSortValue);
43
+ if (urlSortValue === sortType.title_asc || urlSortValue === sortType.title_desc) {
44
+ this.sortOrderBy = urlSortValue;
45
+ } else {
46
+ this.sortOrderBy = sortType.default;
47
+ }
48
+ this.sortVolumes(this.sortOrderBy);
30
49
  }
31
50
 
32
51
  get sortButton() {
33
52
  const sortIcons = {
34
- orig_sort: html`
53
+ default: html`
35
54
  <button class="sort-by neutral-icon" aria-label="Sort volumes in initial order" @click=${() => this.sortVolumes("title_asc")}>${sortNeutralIcon}</button>
36
55
  `,
37
56
  title_asc: html`
38
57
  <button class="sort-by asc-icon" aria-label="Sort volumes in ascending order" @click=${() => this.sortVolumes("title_desc")}>${sortAscIcon}</button>
39
58
  `,
40
59
  title_desc: html`
41
- <button class="sort-by desc-icon" aria-label="Sort volumes in descending order" @click=${() => this.sortVolumes("orig_sort")}>${sortDescIcon}</button>
60
+ <button class="sort-by desc-icon" aria-label="Sort volumes in descending order" @click=${() => this.sortVolumes("default")}>${sortDescIcon}</button>
42
61
  `,
43
62
  };
44
63
 
@@ -46,28 +65,40 @@ export default class VolumesProvider {
46
65
  }
47
66
 
48
67
  /**
49
- * @param {'orig_sort' | 'title_asc' | 'title_desc'} sortByType
68
+ * @param {'default' | 'title_asc' | 'title_desc'} sortByType
50
69
  */
51
70
  sortVolumes(sortByType) {
52
71
  let sortedFiles = [];
53
72
 
54
73
  const files = this.viewableFiles;
55
74
  sortedFiles = files.sort((a, b) => {
56
- if (sortByType === 'orig_sort') return a.orig_sort - b.orig_sort;
57
- else if (sortByType === 'title_asc') return a.title.localeCompare(b.title);
58
- else return b.title.localeCompare(a.title);
75
+ if (sortByType === sortType.title_asc) return a.title.localeCompare(b.title);
76
+ else if (sortByType === sortType.title_desc) return b.title.localeCompare(a.title);
77
+ else return a.orig_sort - b.orig_sort;
59
78
  });
60
79
 
61
80
  this.sortOrderBy = sortByType;
62
81
  this.component.viewableFiles = [...sortedFiles];
63
82
  this.actionButton = this.sortButton;
83
+
84
+ if (this.sortOrderBy !== sortType.default) {
85
+ this.bookreader.urlPlugin.setUrlParam('sort', sortByType);
86
+ } else {
87
+ this.bookreader.urlPlugin.removeUrlParam('sort');
88
+ }
89
+
90
+ const urlSchema = this.bookreader.urlPlugin.urlSchema;
91
+ const urlState = this.bookreader.urlPlugin.urlState;
92
+ this.bookreader.urlPlugin.urlStateToUrlString(urlSchema, urlState);
93
+ this.bookreader.urlPlugin.pushToAddressBar();
94
+
64
95
  this.optionChange(this.bookreader);
65
96
 
66
97
  this.multipleFilesClicked(sortByType);
67
98
  }
68
99
 
69
100
  /**
70
- * @param {'orig_sort' | 'title_asc' | 'title_desc'} orderBy
101
+ * @param {'default' | 'title_asc' | 'title_desc'} orderBy
71
102
  */
72
103
  multipleFilesClicked(orderBy) {
73
104
  if (!window.archive_analytics) {
@@ -50,7 +50,7 @@ BookReader.prototype.init = (function(super_) {
50
50
  this.bind(BookReader.eventNames.PostInit, () => {
51
51
  const { updateWindowTitle, urlMode } = this.options;
52
52
  if (updateWindowTitle) {
53
- document.title = this.shortTitle(50);
53
+ document.title = this.shortTitle(this.bookTitle, 50);
54
54
  }
55
55
  if (urlMode === 'hash') {
56
56
  this.urlStartLocationPolling();
@@ -86,7 +86,7 @@ BookReader.prototype.urlStartLocationPolling = function() {
86
86
  this.oldLocationHash = this.urlReadFragment();
87
87
 
88
88
  if (this.locationPollId) {
89
- clearInterval(this.locationPollID);
89
+ clearInterval(this.locationPollId);
90
90
  this.locationPollId = null;
91
91
  }
92
92
 
@@ -196,3 +196,215 @@ BookReader.prototype.urlReadFragment = function() {
196
196
  BookReader.prototype.urlReadHashFragment = function() {
197
197
  return window.location.hash.substr(1);
198
198
  };
199
+ export class UrlPlugin {
200
+ constructor(options = {}) {
201
+ this.bookReaderOptions = options;
202
+
203
+ // the canonical order of elements is important in the path and query string
204
+ this.urlSchema = [
205
+ { name: 'page', position: 'path', default: 'n0' },
206
+ { name: 'mode', position: 'path', default: '2up' },
207
+ { name: 'search', position: 'path', deprecated_for: 'q' },
208
+ { name: 'q', position: 'query_param' },
209
+ { name: 'sort', position: 'query_param' },
210
+ { name: 'view', position: 'query_param' },
211
+ { name: 'admin', position: 'query_param' },
212
+ ];
213
+
214
+ this.urlState = {};
215
+ this.urlMode = 'hash' || this.options.urlMode;
216
+ this.urlHistoryBasePath = '/';
217
+ this.urlLocationPollId = null;
218
+ this.oldLocationHash = null;
219
+ this.oldUserHash = null;
220
+ }
221
+
222
+ /**
223
+ * Parse JSON object URL state to string format
224
+ * Arrange path names in an order that it is positioned on the urlSchema
225
+ * @param {string} urlState
226
+ * @returns {string}
227
+ */
228
+ urlStateToUrlString(urlState) {
229
+ const searchParams = new URLSearchParams();
230
+ const pathParams = {};
231
+
232
+ Object.keys(urlState).forEach(key => {
233
+ let schema = this.urlSchema.find(schema => schema.name === key);
234
+ if (schema?.deprecated_for) {
235
+ schema = this.urlSchema.find(schemaKey => schemaKey.name === schema.deprecated_for);
236
+ }
237
+ if (schema?.position == 'path') {
238
+ pathParams[schema?.name] = urlState[key];
239
+ } else {
240
+ searchParams.append(schema?.name || key, urlState[key]);
241
+ }
242
+ });
243
+
244
+ const strPathParams = this.urlSchema
245
+ .filter(s => s.position == 'path')
246
+ .map(schema => pathParams[schema.name] ? `${schema.name}/${pathParams[schema.name]}` : '')
247
+ .join('/');
248
+
249
+ const strStrippedTrailingSlash = `${strPathParams.replace(/\/$/, '')}`;
250
+ const concatenatedPath = `/${strStrippedTrailingSlash}?${searchParams.toString()}`;
251
+ return searchParams.toString() ? concatenatedPath : `/${strStrippedTrailingSlash}`;
252
+ }
253
+
254
+ /**
255
+ * Parse string URL and add it in the current urlState
256
+ * Example:
257
+ * /page/n7/mode/2up => {page: 'n7', mode: '2up'}
258
+ * /page/n7/mode/2up/search/hello => {page: 'n7', mode: '2up', q: 'hello'}
259
+ * @param {string} urlString
260
+ * @returns {object}
261
+ */
262
+ urlStringToUrlState(urlString) {
263
+ const urlState = {};
264
+
265
+ // Fetch searchParams from given {urlString}
266
+ // Note: whole URL path is needed for URLSearchParams
267
+ const urlPath = new URL(urlString, 'http://example.com');
268
+ const urlSearchParamsObj = Object.fromEntries(urlPath.searchParams.entries());
269
+ const urlStrSplitSlashObj = Object.fromEntries(urlPath.pathname
270
+ .match(/[^\\/]+\/[^\\/]+/g)
271
+ .map(x => x.split('/'))
272
+ );
273
+ const doesKeyExists = (_object, _key) => {
274
+ return Object.keys(_object).some(value => value == _key);
275
+ };
276
+
277
+ // Add path objects to urlState
278
+ this.urlSchema
279
+ .filter(schema => schema.position == 'path')
280
+ .forEach(schema => {
281
+ if (!urlStrSplitSlashObj[schema.name] && schema.default) {
282
+ return urlState[schema.name] = schema.default;
283
+ }
284
+ const hasPropertyKey = doesKeyExists(urlStrSplitSlashObj, schema.name);
285
+ const hasDeprecatedKey = doesKeyExists(schema, 'deprecated_for') && hasPropertyKey;
286
+
287
+ if (hasDeprecatedKey) {
288
+ urlState[schema.deprecated_for] = urlStrSplitSlashObj[schema.name];
289
+ return;
290
+ }
291
+
292
+ if (hasPropertyKey) {
293
+ urlState[schema.name] = urlStrSplitSlashObj[schema.name];
294
+ return;
295
+ }
296
+ });
297
+
298
+ // Add searchParams to urlState
299
+ // Check if Object value is a Boolean and convert value to Boolean
300
+ // Otherwise, return Object value
301
+ const isBooleanValue = value => value === 'true' || (value === 'false' ? false : value);
302
+ Object.entries(urlSearchParamsObj).forEach(([key, value]) => {
303
+ urlState[key] = isBooleanValue(value);
304
+ });
305
+
306
+ return urlState;
307
+ }
308
+
309
+ /**
310
+ * Add or update key-value to the urlState
311
+ * @param {string} key
312
+ * @param {string} val
313
+ */
314
+ setUrlParam(key, value) {
315
+ this.urlState[key] = value;
316
+
317
+ this.pushToAddressBar();
318
+ }
319
+
320
+ /**
321
+ * Delete key-value to the urlState
322
+ * @param {string} key
323
+ */
324
+ removeUrlParam(key) {
325
+ delete this.urlState[key];
326
+
327
+ this.pushToAddressBar();
328
+ }
329
+
330
+ /**
331
+ * Get key-value from the urlState
332
+ * @param {string} key
333
+ * @return {string}
334
+ */
335
+ getUrlParam(key) {
336
+ return this.urlState[key];
337
+ }
338
+
339
+ /**
340
+ * Push URL params to addressbar
341
+ */
342
+ pushToAddressBar() {
343
+ const urlStrPath = this.urlStateToUrlString(this.urlSchema, this.urlState);
344
+ if (this.urlMode == 'history') {
345
+ if (window.history && window.history.replaceState) {
346
+ const newUrlPath = `${this.urlHistoryBasePath}${urlStrPath}`;
347
+ window.history.replaceState({}, null, newUrlPath);
348
+ }
349
+ } else {
350
+ window.location.replace('#' + urlStrPath);
351
+ }
352
+ this.oldLocationHash = urlStrPath;
353
+ }
354
+
355
+ /**
356
+ * Get the url and check if it has changed
357
+ * If it was changeed, update the urlState
358
+ */
359
+ listenForHashChanges() {
360
+ this.oldLocationHash = window.location.hash.substr(1);
361
+ if (this.urlLocationPollId) {
362
+ clearInterval(this.urlLocationPollId);
363
+ this.urlLocationPollId = null;
364
+ }
365
+
366
+ // check if the URL changes
367
+ const updateHash = () => {
368
+ const newFragment = window.location.hash.substr(1);
369
+ const hasFragmentChange = newFragment != this.oldLocationHash;
370
+
371
+ if (!hasFragmentChange) { return; }
372
+
373
+ this.urlState = this.urlStringToUrlState(newFragment);
374
+ };
375
+ this.urlLocationPollId = setInterval(updateHash, 500);
376
+ }
377
+
378
+ /**
379
+ * Will read either the hash or URL and return the bookreader fragment
380
+ */
381
+ pullFromAddressBar (location = window.location) {
382
+ const path = this.urlMode === 'history'
383
+ ? (location.pathname.substr(this.urlHistoryBasePath.length) + location.search)
384
+ : location.hash.substr(1);
385
+ console.log('path: ', path);
386
+ this.urlState = this.urlStringToUrlState(this.urlSchema, path);
387
+ }
388
+ }
389
+
390
+ export class BookreaderUrlPlugin extends BookReader {
391
+
392
+ init() {
393
+ if (this.options.enableUrlPlugin) {
394
+ this.urlPlugin = new UrlPlugin(this.options);
395
+ this.bind(BookReader.eventNames.PostInit, () => {
396
+ const { urlMode } = this.options;
397
+
398
+ if (urlMode === 'hash') {
399
+ this.urlPlugin.listenForHashChanges();
400
+ }
401
+ });
402
+ }
403
+
404
+ super.init();
405
+ }
406
+
407
+ }
408
+
409
+ window.BookReader = BookreaderUrlPlugin;
410
+ export default BookreaderUrlPlugin;
@@ -1,6 +1,7 @@
1
-
2
1
  import BookReader from '@/src/BookReader.js';
3
2
  import '@/src/plugins/plugin.url.js';
3
+ import { UrlPlugin } from '@/src/plugins/plugin.url.js';
4
+ import sinon from 'sinon';
4
5
 
5
6
  let br;
6
7
  beforeAll(() => {
@@ -10,6 +11,155 @@ beforeAll(() => {
10
11
 
11
12
  afterEach(() => {
12
13
  jest.clearAllMocks();
14
+ sinon.restore();
15
+ });
16
+
17
+
18
+ describe.only('UrlPlugin tests', () => {
19
+ const urlPlugin = new UrlPlugin();
20
+
21
+ describe('urlStateToUrlString tests', () => {
22
+ test('urlStateToUrlString with known states in schema', () => {
23
+ const urlState = { page: 'n7', mode: '1up', search: 'foo' };
24
+ const urlStateWithQueries = { page: 'n7', mode: '1up', q: 'hello', view: 'theater', sort: 'title_asc' };
25
+
26
+ const expectedUrlFromState = '/page/n7/mode/1up?q=foo';
27
+ const expectedUrlFromStateWithQueries = '/page/n7/mode/1up?q=hello&view=theater&sort=title_asc';
28
+
29
+ expect(urlPlugin.urlStateToUrlString(urlState)).toBe(expectedUrlFromState);
30
+ expect(urlPlugin.urlStateToUrlString(urlStateWithQueries)).toBe(expectedUrlFromStateWithQueries);
31
+ });
32
+
33
+ test('urlStateToUrlString with unknown states in schema', () => {
34
+ const urlState = { page: 'n7', mode: '1up' };
35
+ const urlStateWithQueries = { page: 'n7', mode: '1up', q: 'hello', viewer: 'theater', sortBy: 'title_asc' };
36
+
37
+ const expectedUrlFromState = '/page/n7/mode/1up';
38
+ const expectedUrlFromStateWithQueries = '/page/n7/mode/1up?q=hello&viewer=theater&sortBy=title_asc';
39
+
40
+ expect(urlPlugin.urlStateToUrlString(urlState)).toBe(expectedUrlFromState);
41
+ expect(urlPlugin.urlStateToUrlString(urlStateWithQueries)).toBe(expectedUrlFromStateWithQueries);
42
+ });
43
+
44
+ test('urlStateToUrlString with boolean value', () => {
45
+ const urlState = { page: 'n7', mode: '1up', search: 'foo', view: 'theater', wrapper: false };
46
+ const expectedUrlFromState = '/page/n7/mode/1up?q=foo&view=theater&wrapper=false';
47
+
48
+ expect(urlPlugin.urlStateToUrlString(urlState)).toBe(expectedUrlFromState);
49
+ });
50
+ });
51
+
52
+ describe('urlStringToUrlState tests', () => {
53
+ test('urlStringToUrlState without query string', () => {
54
+ const url = '/page/n7/mode/2up';
55
+ const url1 = '/page/n7/mode/1up';
56
+
57
+ expect(urlPlugin.urlStringToUrlState(url)).toEqual({page: 'n7', mode: '2up'});
58
+ expect(urlPlugin.urlStringToUrlState(url1)).toEqual({page: 'n7', mode: '1up'});
59
+ });
60
+
61
+ test('urlStringToUrlState with deprecated_for', () => {
62
+ const url = '/page/n7/mode/2up/search/hello';
63
+
64
+ expect(urlPlugin.urlStringToUrlState(url)).toEqual({page: 'n7', mode: '2up', q: 'hello'});
65
+ });
66
+
67
+ test('urlStringToUrlState with query string', () => {
68
+ const url = '/page/n7/mode/2up/search/hello?view=theather&foo=bar&sort=title_asc';
69
+ const url1 = '/mode/2up?ref=ol&ui=embed&wrapper=false&view=theater';
70
+
71
+ expect(urlPlugin.urlStringToUrlState(url)).toEqual(
72
+ {page: 'n7', mode: '2up', q: 'hello', view: 'theather', foo: 'bar', sort: 'title_asc'}
73
+ );
74
+ expect(urlPlugin.urlStringToUrlState(url1)).toEqual(
75
+ {page: 'n0', mode: '2up', ref: 'ol', ui: 'embed', wrapper: false, view: 'theater'}
76
+ );
77
+ });
78
+
79
+ test('urlStringToUrlState compare search and ?q', () => {
80
+ const url = '/page/n7/mode/2up/search/hello';
81
+ urlPlugin.urlState = { q: 'hello' };
82
+
83
+ expect(urlPlugin.urlStringToUrlState(url)).toEqual({page: 'n7', mode: '2up', q: 'hello'});
84
+ });
85
+ });
86
+
87
+ describe('url plugin helper functions', () => {
88
+ test('setUrlParam', () => {
89
+ urlPlugin.urlState = {};
90
+ urlPlugin.setUrlParam('page', '20');
91
+ urlPlugin.setUrlParam('mode', '2up');
92
+
93
+ expect(urlPlugin.urlState).toEqual({page: '20', mode: '2up'});
94
+ });
95
+
96
+ test('removeUrlParam', () => {
97
+ urlPlugin.setUrlParam('page', '20');
98
+ urlPlugin.setUrlParam('mode', '2up');
99
+ urlPlugin.removeUrlParam('mode');
100
+
101
+ expect(urlPlugin.urlState).toEqual({page: '20'});
102
+ });
103
+
104
+ test('getUrlParam', () => {
105
+ urlPlugin.setUrlParam('page', '20');
106
+ urlPlugin.setUrlParam('mode', '2up');
107
+ expect(urlPlugin.getUrlParam('page')).toEqual('20');
108
+ expect(urlPlugin.getUrlParam('mode')).toEqual('2up');
109
+ });
110
+ });
111
+
112
+ describe('pullFromAddressBar and pushToAddressBar - hash mode', () => {
113
+ test('url without mode state value - use default', () => {
114
+ urlPlugin.urlState = {};
115
+ urlPlugin.urlMode = 'hash';
116
+
117
+ urlPlugin.pullFromAddressBar({ pathname: '/page/12', search: '', hash: '' });
118
+ expect(urlPlugin.urlState).toEqual({page: '12', mode: '2up'});
119
+
120
+ urlPlugin.pushToAddressBar();
121
+ expect(window.location.hash).toEqual('#/page/12/mode/2up');
122
+ });
123
+
124
+ test('url with query param', () => {
125
+ urlPlugin.urlState = {};
126
+ urlPlugin.urlMode = 'hash';
127
+
128
+ urlPlugin.pullFromAddressBar({ pathname: '/page/12', search: '?q=hello&view=theater', hash: '' });
129
+ expect(urlPlugin.urlState).toEqual({page: '12', mode: '2up', q: 'hello', view: 'theater'});
130
+
131
+ urlPlugin.pushToAddressBar();
132
+ expect(window.location.hash).toEqual('#/page/12/mode/2up?q=hello&view=theater');
133
+ });
134
+ });
135
+
136
+ describe('pullFromAddressBar and pushToAddressBar - history mode', () => {
137
+ test('url without mode state value - use default', () => {
138
+ urlPlugin.urlState = {};
139
+ urlPlugin.urlHistoryBasePath = '/details/foo';
140
+ urlPlugin.urlMode = 'history';
141
+
142
+ urlPlugin.pullFromAddressBar({ pathname: '/details/foo/page/12', search: '', hash: '' });
143
+ expect(urlPlugin.urlState).toEqual({page: '12', mode: '2up'});
144
+
145
+ urlPlugin.pushToAddressBar();
146
+ expect(window.location.pathname).toEqual('/details/foo/page/12/mode/2up');
147
+ });
148
+
149
+ test('url with query param', () => {
150
+ urlPlugin.urlState = {};
151
+ urlPlugin.urlHistoryBasePath = '/details/foo';
152
+ urlPlugin.urlMode = 'history';
153
+
154
+ urlPlugin.pullFromAddressBar({ pathname: '/details/foo/page/12', search: '?q=hello&view=theater', hash: '' });
155
+ expect(urlPlugin.urlState).toEqual({page: '12', mode: '2up', q: 'hello', view: 'theater'});
156
+
157
+ urlPlugin.pushToAddressBar();
158
+ const locationUrl = `${window.location.pathname}${window.location.search}`;
159
+ expect(locationUrl).toEqual('/details/foo/page/12/mode/2up?q=hello&view=theater');
160
+ });
161
+ });
162
+
13
163
  });
14
164
 
15
165
  describe('Plugin: URL controller', () => {