@internetarchive/ia-book-actions 0.0.29-alpha37 → 0.0.29-alpha39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "0.0.29-alpha37",
3
+ "version": "0.0.29-alpha39",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -72,15 +72,22 @@ export default class IABookActions extends LitElement {
72
72
  this.borrowType = ''; // (browsed|borrowed)
73
73
  this.consecutiveLoanCounts = 1; // consecutive loan count
74
74
 
75
- // loan auto renew
76
- this.browserTimer = undefined;
75
+ /**
76
+ * contains loan renew time config
77
+ * - totalTime:- number of seconds a loan should last
78
+ * - autoCheckAt:- check for loan renew at last
79
+ * - pageChangedInLast: consider loan renew eligible in last
80
+ * - isDevBox:- it just for testing purpose
81
+ */
77
82
  this.loanRenewConfig = {};
78
83
  this.suppressToast = false;
84
+ this.browseTimer = undefined;
79
85
 
80
86
  /**
81
87
  * contains loan auto-renew response
82
88
  * - texts:- texts messages need to show in toast message
83
89
  * - renewNow:- key to determine if need to renew now
90
+ * - timeLeft:- number of seconds left in active loan
84
91
  */
85
92
  this.loanRenewResult = {
86
93
  texts: null,
@@ -92,7 +99,7 @@ export default class IABookActions extends LitElement {
92
99
  }
93
100
 
94
101
  disconnectedCallback() {
95
- this.tokenPoller.disconnectedInterval();
102
+ this.tokenPoller?.disconnectedInterval();
96
103
  window?.Sentry?.captureMessage(sentryLogs.disconnectedCallback);
97
104
  this.disconnectResizeObserver();
98
105
  }
@@ -213,6 +220,13 @@ export default class IABookActions extends LitElement {
213
220
  return;
214
221
  }
215
222
 
223
+ // initial timer state for 1-hour loan
224
+ if (this.borrowType === 'browsed' && !this.loanRenewResult.renewNow) {
225
+ setTimeout(() => {
226
+ this.resetTimerCountState();
227
+ }, 100);
228
+ }
229
+
216
230
  /**
217
231
  * tokenPoller determines if user has loan token for this book
218
232
  * - if book is going to renew, need to wait until renew is completed
@@ -221,17 +235,13 @@ export default class IABookActions extends LitElement {
221
235
  setTimeout(
222
236
  () => {
223
237
  this.startLoanTokenPoller();
224
-
225
- if (this.borrowType === 'browsed' && !this.loanRenewResult.renewNow) {
226
- this.resetTimerCountState();
227
- }
228
238
  },
229
239
  this.loanRenewResult.renewNow ? this.tokenDelay * 1000 : 100
230
240
  );
231
241
  }
232
242
 
233
243
  /**
234
- * Bind 1 hour loan auto renew,
244
+ * Bind 1 hour loan auto renew event,
235
245
  * There are two events we want to use,
236
246
  * 1. BookReader:pageChanged - dispatched from bookreader side
237
247
  * 2. IABookActions:loanRenew - dispatched from timer-countdown component
@@ -252,17 +262,17 @@ export default class IABookActions extends LitElement {
252
262
  /**
253
263
  * Detech click-event on document to close toast-template
254
264
  */
255
- document.addEventListener('click', () => {
265
+ document.addEventListener('click', e => {
256
266
  if (this.loanRenewHelper && this.loanRenewResult.timeLeft > 0) {
257
267
  console.log('clicked-outside');
258
268
  this.suppressToast = true;
259
269
  }
260
270
 
261
- // reload when user click on page after it EXPIRED
271
+ // reload when user click on book-page and book is EXPIRED
262
272
  const hasExpired =
263
273
  'browsingExpired' in this.lendingStatus &&
264
274
  this.lendingStatus?.browsingExpired;
265
- if (hasExpired) {
275
+ if (hasExpired && e.target.nodeName !== 'IA-BOOK-ACTIONS') {
266
276
  window.location?.reload();
267
277
  }
268
278
  });
@@ -283,6 +293,11 @@ export default class IABookActions extends LitElement {
283
293
  });
284
294
  }
285
295
 
296
+ /**
297
+ * To determine if need to be renewed browsed book
298
+ *
299
+ * @param {Boolean} hasPageChanged
300
+ */
286
301
  async autoLoanRenewChecker(hasPageChanged = false) {
287
302
  this.loanRenewHelper = new LoanRenewHelper(
288
303
  hasPageChanged,
@@ -304,7 +319,9 @@ export default class IABookActions extends LitElement {
304
319
  async showToastMessage() {
305
320
  if (this.suppressToast) return;
306
321
 
307
- const iaBookActions = document.querySelector('ia-book-actions').shadowRoot;
322
+ const iaBookActions = document.querySelector('ia-book-actions')?.shadowRoot;
323
+ if (!iaBookActions) return;
324
+
308
325
  let toastTemplate = iaBookActions.querySelector('toast-template');
309
326
  if (!toastTemplate) {
310
327
  toastTemplate = document.createElement('toast-template');
@@ -338,8 +355,7 @@ export default class IABookActions extends LitElement {
338
355
  }
339
356
 
340
357
  startBrowseTimer() {
341
- clearTimeout(this.browserTimer);
342
-
358
+ clearTimeout(this.browseTimer);
343
359
  const {
344
360
  browsingExpired,
345
361
  user_has_browsed,
@@ -349,7 +365,7 @@ export default class IABookActions extends LitElement {
349
365
  return;
350
366
  }
351
367
 
352
- this.browserTimer = setTimeout(() => {
368
+ this.browseTimer = setTimeout(() => {
353
369
  this.browseHasExpired();
354
370
  }, secondsLeftOnLoan * 1000);
355
371
  }
@@ -358,7 +374,7 @@ export default class IABookActions extends LitElement {
358
374
  * Execute when loan is expired
359
375
  */
360
376
  async browseHasExpired() {
361
- clearTimeout(this.browserTimer);
377
+ this.tokenPoller?.disconnectedInterval();
362
378
 
363
379
  const currStatus = { ...this.lendingStatus, browsingExpired: true };
364
380
  this.lendingStatus = currStatus;
@@ -468,7 +484,7 @@ export default class IABookActions extends LitElement {
468
484
  */
469
485
  async handleLoanAutoRenewed() {
470
486
  if (this.loanRenewResult.renewNow) {
471
- this.tokenPoller.disconnectedInterval();
487
+ this.tokenPoller?.disconnectedInterval();
472
488
 
473
489
  this.suppressToast = false;
474
490
  await this.showToastMessage();
@@ -491,7 +507,7 @@ export default class IABookActions extends LitElement {
491
507
  startLoanTokenPoller() {
492
508
  if (this.tokenPoller) {
493
509
  window?.Sentry?.captureMessage(sentryLogs.clearTokenPoller);
494
- this.tokenPoller.disconnectedInterval();
510
+ this.tokenPoller?.disconnectedInterval();
495
511
  }
496
512
  const successCallback = () => {
497
513
  this.lendingBarPostInit();
@@ -551,7 +567,7 @@ export default class IABookActions extends LitElement {
551
567
  // - clear tokenPoller interval
552
568
  // - set user_has_browsed to `false`
553
569
  if (action === 'create_token') {
554
- this.tokenPoller.disconnectedInterval();
570
+ this.tokenPoller?.disconnectedInterval();
555
571
 
556
572
  const currStatus = {
557
573
  ...this.lendingStatus,
@@ -0,0 +1,47 @@
1
+ import { html, fixture, expect, aTimeout } from '@open-wc/testing';
2
+ import '../../src/components/timer-countdown.js';
3
+
4
+ const container = ({ timeLeftOnLoan, loanRenewConfig } = {}) =>
5
+ html`<timer-countdown
6
+ .timeLeftOnLoan=${timeLeftOnLoan}
7
+ .loanRenewConfig=${loanRenewConfig}
8
+ ></timer-countdown>`;
9
+
10
+ describe('<timer-countdown>', () => {
11
+ it('timer interval is undefined when loan is expired', async () => {
12
+ const el = await fixture(
13
+ container({
14
+ timeLeftOnLoan: 0, // in seconds
15
+ loanRenewConfig: {
16
+ totalTime: 14, // in seconds
17
+ autoCheckAt: 5, // in seconds
18
+ pageChangedInLast: 5, // in seconds
19
+ isDevBox: true,
20
+ },
21
+ })
22
+ );
23
+
24
+ await el.updateComplete;
25
+ expect(el.timerInterval).to.be.undefined;
26
+ });
27
+
28
+ it('timer interval is not undefined when loan is active', async () => {
29
+ const el = await fixture(
30
+ container({
31
+ timeLeftOnLoan: 2, // in seconds
32
+ loanRenewConfig: {
33
+ totalTime: 14, // in seconds
34
+ autoCheckAt: 5, // in seconds
35
+ pageChangedInLast: 5, // in seconds
36
+ isDevBox: true,
37
+ },
38
+ })
39
+ );
40
+
41
+ await aTimeout(1900);
42
+ await el.updateComplete;
43
+
44
+ expect(el.timeLeftOnLoan).to.equal(1);
45
+ expect(el.timerInterval).to.not.be.undefined;
46
+ });
47
+ });
@@ -0,0 +1,85 @@
1
+ import { expect, aTimeout } from '@open-wc/testing';
2
+
3
+ import { LocalCache } from '@internetarchive/local-cache';
4
+ import { LoanRenewHelper } from '../../../src/core/services/loan-renew-helper.js';
5
+
6
+ const identifier = 'booBar';
7
+ const loanRenewConfig = {
8
+ totalTime: 13,
9
+ autoCheckAt: 10,
10
+ pageChangedInLast: 12,
11
+ isDevBox: true,
12
+ };
13
+
14
+ // localCache used for auto-loan-renew
15
+ const localCache = new LocalCache({
16
+ namespace: 'loanRenew',
17
+ });
18
+
19
+ beforeEach(async () => {
20
+ await localCache.set({
21
+ key: `${identifier}-loanTime`,
22
+ value: new Date(new Date().getTime() + loanRenewConfig.totalTime * 1000),
23
+ ttl: Number(loanRenewConfig.totalTime),
24
+ });
25
+ });
26
+
27
+ describe('Loan Renew Determine', () => {
28
+ it('flip book page while reading a book', async () => {
29
+ await aTimeout(1500);
30
+
31
+ await localCache.set({
32
+ key: `${identifier}-pageChangedTime`,
33
+ value: new Date(), // current time
34
+ ttl: Number(loanRenewConfig.totalTime),
35
+ });
36
+
37
+ const loanRenewHelper = new LoanRenewHelper(
38
+ true, // hasPageChanged
39
+ identifier,
40
+ localCache,
41
+ loanRenewConfig
42
+ );
43
+ await loanRenewHelper.handleLoanRenew();
44
+
45
+ expect(loanRenewHelper.result.renewNow).to.be.false;
46
+ });
47
+
48
+ it('auto renewed book when user is currently reading', async () => {
49
+ await localCache.set({
50
+ key: `${identifier}-pageChangedTime`,
51
+ value: new Date(), // current time
52
+ ttl: Number(loanRenewConfig.totalTime),
53
+ });
54
+
55
+ await aTimeout(1500);
56
+
57
+ const loanRenewHelper = new LoanRenewHelper(
58
+ false, // timer-countdown event
59
+ identifier,
60
+ localCache,
61
+ loanRenewConfig
62
+ );
63
+ await loanRenewHelper.handleLoanRenew();
64
+
65
+ expect(loanRenewHelper.result.renewNow).to.be.true;
66
+ });
67
+
68
+ it('get toast message template', async () => {
69
+ const loanRenewHelper = new LoanRenewHelper(
70
+ false,
71
+ identifier,
72
+ localCache,
73
+ loanRenewConfig
74
+ );
75
+
76
+ const toastMsg = loanRenewHelper.getMessageTexts(
77
+ loanRenewHelper.loanReturnWarning,
78
+ '65'
79
+ );
80
+
81
+ expect(toastMsg).to.be.equal(
82
+ 'This book will be automatically returned in 1 minute unless you turn a page.'
83
+ );
84
+ });
85
+ });
@@ -4,11 +4,17 @@ import '../src/ia-book-actions.js';
4
4
  import Sinon from 'sinon';
5
5
 
6
6
  import { SharedResizeObserver } from '@internetarchive/shared-resize-observer';
7
+ import { LocalCache } from '@internetarchive/local-cache';
7
8
 
8
9
  afterEach(() => {
9
10
  Sinon.restore();
10
11
  });
11
12
 
13
+ // localCache used for auto-loan-renew
14
+ const localCache = new LocalCache({
15
+ namespace: 'loanRenew',
16
+ });
17
+
12
18
  const container = ({ userid, identifier, lendingStatus = {} } = {}) =>
13
19
  html`<ia-book-actions
14
20
  .userid=${userid}
@@ -230,9 +236,14 @@ describe('Browsing expired status', () => {
230
236
  },
231
237
  })
232
238
  );
233
- expect(el.primaryTitle).contains('Borrow ends at ');
239
+ expect(el.primaryTitle).contains('');
234
240
  expect(el.primaryActions[0].text).to.equal('Return now');
235
241
  expect(el.primaryActions[1].text).to.equal('Print Disability Access');
242
+
243
+ // default params of one-hour loan renew
244
+ expect(el.loanRenewResult.texts).to.equal(null);
245
+ expect(el.loanRenewResult.renewNow).to.equal(false);
246
+ expect(el.loanRenewResult.timeLeft).to.equal(0);
236
247
  });
237
248
 
238
249
  it('Book is browsing and going to expire after 1 second', async () => {
@@ -248,10 +259,23 @@ describe('Browsing expired status', () => {
248
259
  })
249
260
  );
250
261
 
262
+ // timer-countdown is still active
263
+ expect(el.shadowRoot.querySelector('timer-countdown')).to.not.be.null;
264
+
251
265
  await aTimeout(1500); // wait for 1.5 second
252
266
  await el.updateComplete;
253
267
 
254
268
  expect(el.primaryActions[0].text).to.equal('Borrow');
269
+
270
+ // timer-countdown is gone now
271
+ expect(el.shadowRoot.querySelector('timer-countdown')).to.be.null;
272
+
273
+ // // book has been expired
274
+ expect(el.loanRenewResult.renewNow).to.equal(false);
275
+ expect(el.loanRenewResult.timeLeft).to.equal(0);
276
+ expect(el.loanRenewResult.texts).to.equal(
277
+ 'This book has been automatically returned due to inactivity.'
278
+ );
255
279
  });
256
280
 
257
281
  it('Expiring book cancels interval & emits event', async () => {
@@ -276,9 +300,8 @@ describe('Browsing expired status', () => {
276
300
  el.lendingStatus = browsingStatus;
277
301
  await el.updateComplete;
278
302
 
279
- expect(el.primaryTitle).contains('Borrow ends at ');
303
+ expect(el.primaryTitle).contains('');
280
304
  expect(el.primaryActions[0].text).to.equal('Return now');
281
- expect(el.tokenPoller.loanTokenInterval).to.not.equal(undefined);
282
305
 
283
306
  const expiredStatus = { ...browsingStatus, browsingExpired: true };
284
307
  el.lendingStatus = expiredStatus;
@@ -291,6 +314,50 @@ describe('Browsing expired status', () => {
291
314
  });
292
315
  });
293
316
 
317
+ describe('Auto renew one hour loan', () => {
318
+ it('Book is browsing and renewed it now', async () => {
319
+ const el = await fixture(
320
+ container({
321
+ userid: '@userid',
322
+ identifier: 'Foo',
323
+ lendingStatus: {
324
+ is_lendable: true,
325
+ user_has_browsed: true,
326
+ browseHasExpired: false,
327
+ secondsLeftOnLoan: 12,
328
+ },
329
+ })
330
+ );
331
+
332
+ el.loanRenewResult = {
333
+ texts: 'This book has been renewed for 1 hour.',
334
+ renewNow: true,
335
+ timeLeft: 10,
336
+ };
337
+
338
+ await localCache.set({
339
+ key: `${el.identifier}-loanTime`,
340
+ value: new Date(new Date().getTime() + 10 * 1000),
341
+ ttl: Number(10),
342
+ });
343
+
344
+ const myEvent = new CustomEvent('loanAutoRenewed');
345
+ el.addEventListener('loanAutoRenewed', () => {
346
+ el.handleLoanAutoRenewed();
347
+ });
348
+ el.dispatchEvent(myEvent);
349
+
350
+ await aTimeout(1500); // wait for 1.5 second
351
+ await el.updateComplete;
352
+
353
+ expect(el.loanRenewResult.timeLeft).to.equal(10);
354
+ expect(el.loanRenewResult.renewNow).to.equal(true);
355
+ expect(el.loanRenewResult.texts).to.equal(
356
+ 'This book has been renewed for 1 hour.'
357
+ );
358
+ });
359
+ });
360
+
294
361
  describe('Shared Resize Observer', () => {
295
362
  it('can receive a Shared Resize Observer', async () => {
296
363
  const sharedObserverStub = new SharedResizeObserver();