@internetarchive/ia-book-actions 0.0.29-alpha4 → 0.0.29-alpha40

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.
@@ -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 = ({ secondsLeftOnLoan, loanRenewTimeConfig } = {}) =>
5
+ html`<timer-countdown
6
+ .secondsLeftOnLoan=${secondsLeftOnLoan}
7
+ .loanRenewTimeConfig=${loanRenewTimeConfig}
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
+ secondsLeftOnLoan: 0, // in seconds
15
+ loanRenewTimeConfig: {
16
+ loanTotalTime: 14, // in seconds
17
+ loanRenewAtLast: 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
+ secondsLeftOnLoan: 2, // in seconds
32
+ loanRenewTimeConfig: {
33
+ loanTotalTime: 14, // in seconds
34
+ loanRenewAtLast: 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.secondsLeftOnLoan).to.equal(2);
45
+ expect(el.timerInterval).to.not.be.undefined;
46
+ });
47
+ });
@@ -25,7 +25,7 @@ describe('Get Lending Actions', () => {
25
25
 
26
26
  expect(actions.primaryTitle).to.equal(bookTitles.available_1hr);
27
27
  expect(actions.primaryActions.length).to.equal(3);
28
- expect(actions.primaryActions[0].text).to.equal('Borrow for 1 hour');
28
+ expect(actions.primaryActions[0].text).to.equal('Borrow');
29
29
  expect(actions.primaryActions[1].text).to.equal('Borrow for 14 days');
30
30
  expect(actions.secondaryActions[1].text).to.equal('Purchase at');
31
31
  });
@@ -111,7 +111,7 @@ describe('Get Lending Actions', () => {
111
111
  expect(actions.primaryTitle).to.equal(bookTitles.claim_waitlist);
112
112
  expect(actions.primaryActions.length).to.equal(3);
113
113
  expect(actions.primaryActions[0].text).to.equal('Borrow for 14 days');
114
- expect(actions.primaryActions[1].text).to.equal('Borrow for 1 hour');
114
+ expect(actions.primaryActions[1].text).to.equal('Borrow');
115
115
  expect(actions.primaryActions[2].text).to.equal('Leave Waitlist');
116
116
  expect(spy.called).to.be.true;
117
117
  });
@@ -138,7 +138,7 @@ describe('Get Lending Actions', () => {
138
138
  expect(actions.primaryTitle).to.equal(bookTitles.on_waitlist);
139
139
  expect(actions.primaryActions.length).to.equal(2);
140
140
  expect(actions.primaryActions[0].text).to.equal('Leave Waitlist');
141
- expect(actions.primaryActions[1].text).to.equal('Borrow for 1 hour');
141
+ expect(actions.primaryActions[1].text).to.equal('Borrow');
142
142
  expect(claimSpy.called).to.be.false;
143
143
  expect(waitlistSpy.called).to.be.true;
144
144
  });
@@ -162,7 +162,7 @@ describe('Get Lending Actions', () => {
162
162
  expect(actions.primaryTitle).to.equal(bookTitles.on_waitlist);
163
163
  expect(actions.primaryActions.length).to.equal(2);
164
164
  expect(actions.primaryActions[0].text).to.equal('Leave Waitlist');
165
- expect(actions.primaryActions[1].text).to.equal('Borrow for 1 hour');
165
+ expect(actions.primaryActions[1].text).to.equal('Borrow');
166
166
  expect(waitlistSpy.called).to.be.true;
167
167
  });
168
168
  });
@@ -0,0 +1,87 @@
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 loanRenewTimeConfig = {
8
+ loanTotalTime: 13,
9
+ loanRenewAtLast: 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(
23
+ new Date().getTime() + loanRenewTimeConfig.loanTotalTime * 1000
24
+ ),
25
+ ttl: Number(loanRenewTimeConfig.loanTotalTime),
26
+ });
27
+ });
28
+
29
+ describe('Loan Renew Determine', () => {
30
+ it('flip book page while reading a book', async () => {
31
+ await aTimeout(1500);
32
+
33
+ await localCache.set({
34
+ key: `${identifier}-pageChangedTime`,
35
+ value: new Date(), // current time
36
+ ttl: Number(loanRenewTimeConfig.loanTotalTime),
37
+ });
38
+
39
+ const loanRenewHelper = new LoanRenewHelper(
40
+ true, // hasPageChanged
41
+ identifier,
42
+ localCache,
43
+ loanRenewTimeConfig
44
+ );
45
+ await loanRenewHelper.handleLoanRenew();
46
+
47
+ expect(loanRenewHelper.result.renewNow).to.be.false;
48
+ });
49
+
50
+ it('auto renewed book when user is currently reading', async () => {
51
+ await localCache.set({
52
+ key: `${identifier}-pageChangedTime`,
53
+ value: new Date(), // current time
54
+ ttl: Number(loanRenewTimeConfig.loanTotalTime),
55
+ });
56
+
57
+ await aTimeout(1500);
58
+
59
+ const loanRenewHelper = new LoanRenewHelper(
60
+ false, // timer-countdown event
61
+ identifier,
62
+ localCache,
63
+ loanRenewTimeConfig
64
+ );
65
+ await loanRenewHelper.handleLoanRenew();
66
+
67
+ expect(loanRenewHelper.result.renewNow).to.be.true;
68
+ });
69
+
70
+ it('get toast message template', async () => {
71
+ const loanRenewHelper = new LoanRenewHelper(
72
+ false,
73
+ identifier,
74
+ localCache,
75
+ loanRenewTimeConfig
76
+ );
77
+
78
+ const toastMsg = loanRenewHelper.getMessageTexts(
79
+ loanRenewHelper.loanReturnWarning,
80
+ '65' // in seconds
81
+ );
82
+
83
+ expect(toastMsg).to.be.equal(
84
+ 'This book will be automatically returned in 1 minute unless you turn a page.'
85
+ );
86
+ });
87
+ });
@@ -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}
@@ -81,7 +87,7 @@ describe('Primary Actions data', () => {
81
87
  );
82
88
  const expectedPrimaryActions = [
83
89
  {
84
- text: 'Borrow for 1 hour',
90
+ text: 'Borrow', // changed from 'Borrow for 1 hour'
85
91
  callback: () => {},
86
92
  className: 'ia-button primary',
87
93
  },
@@ -161,7 +167,7 @@ describe('Borrow status actions', () => {
161
167
 
162
168
  // removed available_to_borrow from lending bar
163
169
  expect(el.lendingStatus.available_to_borrow).to.be.false;
164
- expect(el.primaryActions[0].text).to.equal('Borrow for 1 hour');
170
+ expect(el.primaryActions[0].text).to.equal('Borrow');
165
171
  });
166
172
 
167
173
  it('Check action for borrowable book without user', async () => {
@@ -182,9 +188,7 @@ describe('Borrow status actions', () => {
182
188
  },
183
189
  ];
184
190
 
185
- expect(el.primaryTitle).to.equal(
186
- 'Renewable every hour, pending availability.'
187
- );
191
+ expect(el.primaryTitle).to.equal('');
188
192
  expect(el.primaryActions.length).to.equal(2);
189
193
  expect(el.primaryActions[0].text).to.equal('Log In and Borrow');
190
194
  expect(el.primaryActions[0].text).to.equal(expectedPrimaryActions[0].text);
@@ -232,9 +236,14 @@ describe('Browsing expired status', () => {
232
236
  },
233
237
  })
234
238
  );
235
- expect(el.primaryTitle).contains('Borrow ends at ');
239
+ expect(el.primaryTitle).contains('');
236
240
  expect(el.primaryActions[0].text).to.equal('Return now');
237
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('');
245
+ expect(el.loanRenewResult.renewNow).to.equal(false);
246
+ expect(el.loanRenewResult.secondsLeft).to.equal(0);
238
247
  });
239
248
 
240
249
  it('Book is browsing and going to expire after 1 second', async () => {
@@ -250,11 +259,23 @@ describe('Browsing expired status', () => {
250
259
  })
251
260
  );
252
261
 
262
+ // timer-countdown is still active
263
+ expect(el.shadowRoot.querySelector('timer-countdown')).to.not.be.null;
264
+
253
265
  await aTimeout(1500); // wait for 1.5 second
254
266
  await el.updateComplete;
255
267
 
256
- expect(el.primaryTitle).contains('Your loan has expired.');
257
- expect(el.primaryActions[0].text).to.equal('Borrow again');
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.secondsLeft).to.equal(0);
276
+ expect(el.loanRenewResult.texts).to.equal(
277
+ 'This book has been automatically returned due to inactivity.'
278
+ );
258
279
  });
259
280
 
260
281
  it('Expiring book cancels interval & emits event', async () => {
@@ -279,9 +300,8 @@ describe('Browsing expired status', () => {
279
300
  el.lendingStatus = browsingStatus;
280
301
  await el.updateComplete;
281
302
 
282
- expect(el.primaryTitle).contains('Borrow ends at ');
303
+ expect(el.primaryTitle).contains('');
283
304
  expect(el.primaryActions[0].text).to.equal('Return now');
284
- expect(el.tokenPoller.loanTokenInterval).to.not.equal(undefined);
285
305
 
286
306
  const expiredStatus = { ...browsingStatus, browsingExpired: true };
287
307
  el.lendingStatus = expiredStatus;
@@ -289,12 +309,55 @@ describe('Browsing expired status', () => {
289
309
  await aTimeout(1500); // wait for 1.5 sec
290
310
 
291
311
  expect(eventReceived).to.equal(true);
292
- expect(el.primaryTitle).contains('Your loan has expired.');
293
- expect(el.primaryActions[0].text).to.equal('Borrow again');
312
+ expect(el.primaryActions[0].text).to.equal('Borrow');
294
313
  expect(el.tokenPoller.loanTokenInterval).to.equal(undefined);
295
314
  });
296
315
  });
297
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
+ secondsLeft: 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.texts).to.equal(
354
+ 'This book has been renewed for 1 hour.'
355
+ );
356
+ expect(el.loanRenewResult.renewNow).to.equal(true);
357
+ expect(el.loanRenewResult.secondsLeft).to.equal(10);
358
+ });
359
+ });
360
+
298
361
  describe('Shared Resize Observer', () => {
299
362
  it('can receive a Shared Resize Observer', async () => {
300
363
  const sharedObserverStub = new SharedResizeObserver();
@@ -1,58 +0,0 @@
1
- import { css } from 'lit';
2
-
3
- export default timerCountDownStyle = css`
4
- :host {
5
- right: 5px;
6
- font-size: 14px;
7
- position: absolute;
8
- }
9
- #countdown1 {
10
- position: relative;
11
- margin: auto;
12
- margin-top: 100px;
13
- height: 40px;
14
- width: 40px;
15
- text-align: center;
16
- }
17
-
18
- #countdown-number {
19
- color: white;
20
- display: inline-block;
21
- line-height: 40px;
22
- margin-right: 12px;
23
- }
24
-
25
- svg {
26
- position: absolute;
27
- top: 0;
28
- right: 0;
29
- width: 40px;
30
- height: 40px;
31
- transform: rotateY(-180deg) rotateZ(-90deg);
32
- }
33
-
34
- svg circle {
35
- stroke-dasharray: 113px;
36
- stroke-dashoffset: 0px;
37
- stroke-linecap: round;
38
- stroke-width: 3px;
39
- stroke: white;
40
- fill: none;
41
- animation: countdown 60s linear infinite forwards;
42
- }
43
-
44
- @keyframes countdown {
45
- from {
46
- stroke-dashoffset: 0px;
47
- }
48
- to {
49
- stroke-dashoffset: 113px;
50
- }
51
- }
52
- .hidden {
53
- display: none;
54
- }
55
- .visible {
56
- display: inline-block;
57
- }
58
- `;
@@ -1,76 +0,0 @@
1
- import { html, css, LitElement, nothing } from 'lit';
2
-
3
- export class ToastMessage extends LitElement {
4
- static get properties() {
5
- return {
6
- texts: { type: String },
7
- dismisOnClick: { type: Boolean },
8
- hideToast: { type: Boolean },
9
- };
10
- }
11
-
12
- constructor() {
13
- super();
14
- this.texts = '';
15
- this.textClass = '';
16
- this.hideToast = false;
17
- }
18
-
19
- render() {
20
- return !this.hideToast
21
- ? html`
22
- <span
23
- class="toast-message fade-in ${this.textClass}"
24
- title="Click/tap to close"
25
- @click=${() => { this.dismisOnClick ? this.hideToast = true : nothing}}
26
- >${this.texts}</span>
27
- `
28
- : nothing;
29
- }
30
-
31
- static get styles() {
32
- const heightFromTop = css`var(--messageHeightFromTop, 15%)`;
33
-
34
- return css`
35
- :host {
36
- display: inline-block;
37
-
38
- }
39
- .toast-message {
40
- z-index: 2;
41
- position: absolute;
42
- top: ${heightFromTop};
43
- font-size: 1.4rem;
44
- font-family: "Helvetica Neue";
45
- color: #fff;
46
- left: 50%;
47
- background: #333;
48
- border: 1px solid #fff;
49
- border-radius: 5px;
50
- box-shadow: 1px 1px 2px;
51
- padding: 10px;
52
- width: fit-content;
53
- transform: translate(-50%, -50%);
54
- cursor: pointer;
55
- animation: fadeIn 5s;
56
- @keyframes fadeIn {
57
- 0% {
58
- opacity: 0;
59
- }
60
- 100% {
61
- opacity: 1;
62
- }
63
- }
64
- }
65
-
66
- .hidden {
67
- display: none;
68
- }
69
- .visible {
70
- display: inline-block;
71
- }
72
- `;
73
- }
74
- }
75
-
76
- window.customElements.define('toast-message', ToastMessage);
@@ -1,152 +0,0 @@
1
- import ActionsHandlerService from './actions-handler/actions-handler-service.js';
2
-
3
- /**
4
- * This class is used to determine if use is eligible for auto renew loan.
5
- *
6
- * ActionsHandlerService is a function being used to execute
7
- */
8
- export class OneHourAutoRenew {
9
- constructor(pageClick, identifier, localCache, autoRenewConfig, userHasBrowsed) {
10
- this.pageClick = pageClick;
11
- this.identifier = identifier;
12
- this.localCache = localCache;
13
- this.autoRenewConfig = autoRenewConfig;
14
- this.userHasBrowsed = userHasBrowsed;
15
-
16
- // messages for auto return machenism
17
- this.autoRenewMessage = 'This book has been renewed for another hour.';
18
- this.autoReturnMessage = 'This book has been auto-returned due to inactivity.';
19
- this.autoReturnWarning = 'This book will be auto-returned in 10 minutes unless you turn a page.';
20
-
21
- // private props
22
- this.resultRes = {
23
- toastTexts: this.autoReturnMessage,
24
- hideToast: true,
25
- renewNow: false,
26
- };
27
-
28
- return this.handleBookAutoRenew();
29
- }
30
-
31
- async handleBookAutoRenew() {
32
- try {
33
- // if page is not flipped in last 15 minutes,
34
- // instantaly renew it if user viewed new page in last 10 minutes
35
- if (this.pageClick) {
36
- console.log('page flipped!')
37
- this.setPageFlippedTime();
38
- return this.userFlippedBookPage(); // user clicked on page
39
- } else {
40
- console.log('auto checker!')
41
- return this.autoCheckerTimer(); // auto checker at 50th minute
42
- }
43
-
44
- } catch (error) {
45
- console.log(error)
46
- }
47
- }
48
-
49
- async setPageFlippedTime() {
50
- // set pageFlipTime time in localStorage
51
- await this.localCache.set({
52
- key: `${this.identifier}-pageFlipTime`,
53
- value: new Date(), // current time
54
- ttl: parseInt(this.autoRenewConfig.totalTime),
55
- })
56
- }
57
-
58
- async userFlippedBookPage() {
59
- const {
60
- totalTime, // 60
61
- autoCheckAt, // 50
62
- } = this.autoRenewConfig;
63
- const currentDate = new Date();
64
- const loanTime = await this.localCache.get(`${this.identifier}-loanTime`);
65
-
66
- const lastTimeFrame = await this.changeTime(
67
- loanTime,
68
- totalTime - autoCheckAt,
69
- 'add'
70
- ); // 50 seconds
71
-
72
- if (currentDate >= lastTimeFrame) {
73
- console.log('1 YES NOW BORROW IT AGAIN NOW!');
74
- this.resultRes.toastTexts = this.autoRenewMessage;
75
- this.resultRes.browseAgainNow = true;
76
- this.resultRes.hideToast = false;
77
- this.resultRes.renewNow = true;
78
- }
79
-
80
- return this.resultRes;
81
- }
82
-
83
- async autoCheckerTimer() {
84
- const currentDate = new Date();
85
- const { FlippedInLast } = this.autoRenewConfig;
86
- const lastPageFlipTime = await this.localCache.get(`${this.identifier}-pageFlipTime`);
87
-
88
- // last 15 min if used flipped a page.
89
- const lastFlipTimeFrame = await this.changeTime(
90
- currentDate,
91
- FlippedInLast,
92
- 'sub'
93
- ); // 15 seconds
94
-
95
- if (lastPageFlipTime === undefined || lastPageFlipTime <= lastFlipTimeFrame) { // not viewed
96
- console.log('2 not viewed, warning message!');
97
- this.resultRes.toastTexts = this.autoReturnWarning;
98
- this.resultRes.hideToast = false;
99
- } else if (lastPageFlipTime >= lastFlipTimeFrame) { // viewed in last time frame
100
- console.log('3 viewed, auto renew and message!');
101
- this.resultRes.toastTexts = this.autoRenewMessage;
102
- this.resultRes.browseAgainNow = true;
103
- this.resultRes.hideToast = false;
104
- this.resultRes.renewNow = true;
105
-
106
- // delete pageFlipTime when book is auto renew at nth minute
107
- await this.localCache.delete(`${this.identifier}-pageFlipTime`);
108
- }
109
-
110
- return this.resultRes;
111
- }
112
-
113
- async changeTime(date, minutes, op) {
114
- if (date === undefined) return undefined;
115
-
116
- if (op === 'sub') {
117
- return new Date(date.getTime() - minutes * 1000);
118
- }
119
-
120
- return new Date(date.getTime() + minutes * 1000);
121
- }
122
-
123
-
124
- sendEvent(eventCategory, eventAction) {
125
- window?.archive_analytics?.send_event(
126
- eventCategory,
127
- eventAction,
128
- `identifier=${this.identifier}`
129
- );
130
- }
131
-
132
- async handleAutoRenew() {
133
- const action = 'renew_loan';
134
- ActionsHandlerService({
135
- identifier: this.identifier,
136
- action,
137
- error: data => {
138
- window?.Sentry?.captureMessage(
139
- 'handleAutoRenew error',
140
- JSON.stringify(data)
141
- );
142
- // this.errorCallback({ detail: { action, data } });
143
- // send LendingServiceError to GA
144
- this.sendEvent('handleAutoRenew', action);
145
- },
146
- success: () => {
147
- console.log('success!');
148
- return 'success';
149
- },
150
- });
151
- }
152
- }