@internetarchive/ia-book-actions 0.0.6-5 → 0.0.6-8-01

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/demo/index.html CHANGED
@@ -71,6 +71,16 @@
71
71
  float: left;
72
72
  margin: 2px;
73
73
  }
74
+
75
+ #ui-overlay {
76
+ position: fixed;
77
+ top: 0;
78
+ left: 0;
79
+ width: 100%;
80
+ height: 100%;
81
+ background: black;
82
+ opacity: 0.3;
83
+ }
74
84
  </style>
75
85
  </head>
76
86
 
@@ -146,7 +156,7 @@
146
156
  if (e.target.value === 'user_has_browsed') {
147
157
  lendingStatus1.user_has_browsed = true;
148
158
  lendingStatus1.available_to_browse = false;
149
- lendingStatus1.secondsLeftOnLoan = 10;
159
+ lendingStatus1.secondsLeftOnLoan = 30;
150
160
  lendingStatus1.browsingExpired = false;
151
161
  } else if(e.target.value === 'browsingExpired') {
152
162
  lendingStatus1.user_has_browsed = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/ia-book-actions",
3
- "version": "0.0.6-5",
3
+ "version": "0.0.6-8-01",
4
4
  "description": "Bookreader actions for borrowable book",
5
5
  "author": "Internet Archive",
6
6
  "license": "AGPL-3.0-only",
@@ -21,6 +21,7 @@
21
21
  "@internetarchive/ia-icons": "^1.2.2",
22
22
  "@internetarchive/icon-bookmark": "^1.1.3",
23
23
  "@internetarchive/icon-info": "^1.2.6",
24
+ "@internetarchive/icon-close": "^1.1.3",
24
25
  "@internetarchive/shared-resize-observer": "^0.0.1",
25
26
  "lit-element": "^2.4.0",
26
27
  "lit-html": "^1.3.0"
@@ -6,6 +6,9 @@ export default css`
6
6
  height: 3.5rem;
7
7
  padding: 1rem 0;
8
8
  }
9
+ .actiongroup {
10
+ display: flex;
11
+ }
9
12
  .action-buttons {
10
13
  display: inline-flex;
11
14
  align-items: center;
@@ -27,6 +27,7 @@ export class CollapsibleActionGroup extends ActionsHandler {
27
27
  hasAdminAccess: { type: Boolean },
28
28
  dropdownArrow: { type: String },
29
29
  disabled: { type: Boolean },
30
+ bookHasBrowsed: { type: Boolean },
30
31
  };
31
32
  }
32
33
 
@@ -45,6 +46,13 @@ export class CollapsibleActionGroup extends ActionsHandler {
45
46
  this.title = '';
46
47
  this.loaderIcon = 'https://archive.org/upload/images/tree/loading.gif';
47
48
  this.disabled = false;
49
+ this.bookHasBrowsed = false;
50
+ }
51
+
52
+ firstUpdated() {
53
+ if (this.bookHasBrowsed) {
54
+ this.emitFetchLoanToken();
55
+ }
48
56
  }
49
57
 
50
58
  updated(changed) {
@@ -53,6 +61,26 @@ export class CollapsibleActionGroup extends ActionsHandler {
53
61
  this.resetActions();
54
62
  }
55
63
  }
64
+
65
+ // this is execute on demo page only
66
+ if (
67
+ changed.has('bookHasBrowsed') &&
68
+ window.location.pathname === '/demo/'
69
+ ) {
70
+ this.emitFetchLoanToken();
71
+ }
72
+ }
73
+
74
+ /* emit custom event to fetch loan token */
75
+ emitFetchLoanToken() {
76
+ this.dispatchEvent(
77
+ new CustomEvent('fetchLoanToken', {
78
+ detail: {
79
+ event: { category: 'action' },
80
+ bookHasBrowsed: this.bookHasBrowsed,
81
+ },
82
+ })
83
+ );
56
84
  }
57
85
 
58
86
  /**
@@ -0,0 +1,144 @@
1
+ /* eslint-disable */
2
+ import { LitElement, html, css } from 'lit-element';
3
+ import { classMap } from 'lit-html/directives/class-map';
4
+
5
+ import '@internetarchive/icon-close/icon-close.js';
6
+ import buttonBaseStyle from '../assets/styles/ia-button.js';
7
+
8
+ export class ShowDialog extends LitElement {
9
+ static get properties() {
10
+ return {
11
+ title: { type: String },
12
+ body: { type: String },
13
+ actions: { type: Array },
14
+ opened: { type: Boolean },
15
+ allowClose: { type: Boolean },
16
+ };
17
+ }
18
+
19
+ constructor() {
20
+ super();
21
+ this.title = 'Error';
22
+ this.body =
23
+ 'Unexpected error: loan does not exist. Please try deleting your archive.org cookie.';
24
+ this.actions = [];
25
+ this.opened = false;
26
+ this.allowClose = false;
27
+ }
28
+
29
+ updated(changed) {
30
+ if (changed.get('opened') === false) {
31
+ this.addOverlay();
32
+ } else {
33
+ this.removeOverlay();
34
+ }
35
+ }
36
+
37
+ render() {
38
+ return html`
39
+ <div
40
+ class="${classMap({
41
+ dialog: true,
42
+ opened: this.opened,
43
+ closed: !this.opened,
44
+ })}"
45
+ >
46
+ <div class="dialog-head">${this.title} ${this.closeButton}</div>
47
+ <div class="dialog-body">${this.body}</div>
48
+ <div class="dialog-foot">${this.renderActionButton}</div>
49
+ </div>
50
+ `;
51
+ }
52
+
53
+ get closeButton() {
54
+ return this.allowClose
55
+ ? html`<ia-icon-close
56
+ @click="${() =>
57
+ this.dispatchEvent(new CustomEvent('dialogAlertClose'))}"
58
+ ></ia-icon-close>`
59
+ : html``;
60
+ }
61
+
62
+ /* exceptional case to use dom element */
63
+ addOverlay() {
64
+ if (this.opened && !document.getElementById('ui-overlay')) {
65
+ const overlayElement = document.createElement('div');
66
+ overlayElement.id = 'ui-overlay';
67
+ document.getElementsByTagName('body')[0].appendChild(overlayElement);
68
+ }
69
+ }
70
+
71
+ /* exceptional case to use dom element */
72
+ removeOverlay() {
73
+ const overlayElement = document.getElementById('ui-overlay');
74
+ if (overlayElement) overlayElement.remove();
75
+ }
76
+
77
+ get renderActionButton() {
78
+ if (!this.actions) return;
79
+
80
+ return this.actions.map(
81
+ action =>
82
+ html`<a class=${action.className} href=${action.href}>
83
+ ${action.text}
84
+ </button>`
85
+ );
86
+ }
87
+
88
+ static get styles() {
89
+ const dialogStyle = css`
90
+ :host {
91
+ color: #343434;
92
+ font-size: 1.6rem;
93
+ }
94
+ .dialog {
95
+ position: absolute;
96
+ background: #fff;
97
+ border-radius: 1.2rem;
98
+ z-index: 3000;
99
+ flex-direction: column;
100
+ min-width: 300px;
101
+ max-width: 400px;
102
+ left: 50%;
103
+ transform: translateX(-50%);
104
+
105
+ border: 2px solid #ccc;
106
+ border-radius: 12px;
107
+ box-shadow: 0px 0px 27px 6px rgb(66 66 66 / 80%);
108
+ }
109
+ .dialog-head,
110
+ .dialog-body,
111
+ .dialog-foot {
112
+ padding: 1rem;
113
+ user-select: none;
114
+ text-align: left;
115
+ }
116
+ .dialog-body {
117
+ border: 1px solid #d8d8d8;
118
+ }
119
+ ia-icon-close {
120
+ position: absolute;
121
+ right: 0.5rem;
122
+ top: 0.5rem;
123
+ width: 2.5rem;
124
+ height: 2.5rem;
125
+ vertical-align: middle;
126
+ --iconFillColor: #333;
127
+ cursor: pointer;
128
+ }
129
+ .opened {
130
+ display: flex;
131
+ }
132
+ .closed {
133
+ display: none;
134
+ }
135
+ a {
136
+ text-decoration: none;
137
+ }
138
+ `;
139
+
140
+ return [buttonBaseStyle, dialogStyle];
141
+ }
142
+ }
143
+
144
+ customElements.define('show-dialog', ShowDialog);
@@ -8,17 +8,14 @@ export default function ActionsHandlerService(options) {
8
8
  const option = {
9
9
  action: null,
10
10
  identifier: '',
11
- data: {},
12
11
  success() {},
13
12
  error() {},
14
- loader: true,
15
- type: 'POST',
16
13
  ...options,
17
14
  };
18
15
 
19
16
  let baseHost = '';
20
17
  if (window.location.pathname === '/demo/') {
21
- baseHost = `/demo/1`;
18
+ baseHost = `/demo/`;
22
19
  } else {
23
20
  baseHost = `/services/loans/loan`;
24
21
  }
@@ -34,9 +31,20 @@ export default function ActionsHandlerService(options) {
34
31
  .then(response => {
35
32
  // test changes, (won't affect you)
36
33
  if (baseHost == '/demo/1' || baseHost == '/demo/') {
34
+ if (option.action == 'create_token') {
35
+ return {
36
+ success: false,
37
+ error: 'loan token not found. please try again later.',
38
+ };
39
+ return {
40
+ success: true,
41
+ token: '1640945898-f77a0cb615190798f1faf2dafe277b2e',
42
+ };
43
+ }
44
+
37
45
  return {
38
- error: 'This book is not available to browse at this time. Please try again later.',
39
- // error: 'This book is not available to borrow at this time. Please try again later.',
46
+ error:
47
+ 'This book is not available to borrow at this time. Please try again later.',
40
48
  };
41
49
  }
42
50
 
@@ -46,7 +54,6 @@ export default function ActionsHandlerService(options) {
46
54
  })
47
55
  .then(data => {
48
56
  // `data` is the parsed version of the JSON returned from the above endpoint.
49
- console.log('actual reponse for endpoint', data);
50
57
  if (!data.error) {
51
58
  option?.success(data);
52
59
  } else {
@@ -15,6 +15,9 @@ export default class ActionsHandler extends LitElement {
15
15
  super();
16
16
  this.identifier = identifier;
17
17
  this.ajaxTimeout = 6000;
18
+ this.loanTokenPollingDelay =
19
+ window.location.pathname === '/demo/' ? 2000 : 20000; // 20000 ms = 20 sec
20
+ this.loanTokenInterval = null;
18
21
  this.bindEvents();
19
22
  }
20
23
 
@@ -84,11 +87,27 @@ export default class ActionsHandler extends LitElement {
84
87
  const { category, action } = detail.event;
85
88
  this.sendEvent(category, action);
86
89
  });
90
+
91
+ this.addEventListener('fetchLoanToken', ({ detail }) => {
92
+ const bookHasBrowsed = detail?.bookHasBrowsed;
93
+
94
+ // fetch loan token and set an interval
95
+ if (bookHasBrowsed) {
96
+ console.log('token poll started...');
97
+
98
+ this.loanTokenInterval = setInterval(() => {
99
+ this.handleLoanTokenPoller();
100
+ }, this.loanTokenPollingDelay);
101
+ } else {
102
+ // if book is not browsed, just clear token polling interval
103
+ clearInterval(this.loanTokenInterval);
104
+ }
105
+ });
87
106
  }
88
107
 
89
108
  handleBrowseIt() {
90
109
  const context = 'browse_book';
91
- this.toggleActionBarState(context);
110
+ this.ActionError(context);
92
111
 
93
112
  ActionsHandlerService({
94
113
  action: context,
@@ -97,15 +116,14 @@ export default class ActionsHandler extends LitElement {
97
116
  this.handleReadItNow();
98
117
  },
99
118
  error: data => {
100
- alert(data.error);
101
- this.toggleActionBarState(context, data);
119
+ this.ActionError(context, data);
102
120
  },
103
121
  });
104
122
  }
105
123
 
106
124
  handleReturnIt() {
107
125
  const context = 'return_loan';
108
- this.toggleActionBarState(context);
126
+ this.ActionError(context);
109
127
 
110
128
  ActionsHandlerService({
111
129
  action: context,
@@ -115,15 +133,14 @@ export default class ActionsHandler extends LitElement {
115
133
  URLHelper.goToUrl(`/details/${this.identifier}`, true);
116
134
  },
117
135
  error: data => {
118
- alert(data.error);
119
- this.toggleActionBarState(context);
136
+ this.ActionError(context, data);
120
137
  },
121
138
  });
122
139
  }
123
140
 
124
141
  handleBorrowIt() {
125
142
  const context = 'borrow_book';
126
- this.toggleActionBarState(context);
143
+ this.ActionError(context);
127
144
 
128
145
  ActionsHandlerService({
129
146
  action: context,
@@ -132,15 +149,14 @@ export default class ActionsHandler extends LitElement {
132
149
  this.handleReadItNow();
133
150
  },
134
151
  error: data => {
135
- alert(data.error);
136
- this.toggleActionBarState(context, data);
152
+ this.ActionError(context, data);
137
153
  },
138
154
  });
139
155
  }
140
156
 
141
157
  handleReserveIt() {
142
158
  const context = 'join_waitlist';
143
- this.toggleActionBarState(context);
159
+ this.ActionError(context);
144
160
 
145
161
  ActionsHandlerService({
146
162
  action: context,
@@ -149,15 +165,14 @@ export default class ActionsHandler extends LitElement {
149
165
  URLHelper.goToUrl(URLHelper.getRedirectUrl(), true);
150
166
  },
151
167
  error: data => {
152
- alert(data.error);
153
- this.toggleActionBarState(context);
168
+ this.ActionError(context, data);
154
169
  },
155
170
  });
156
171
  }
157
172
 
158
173
  handleRemoveFromWaitingList() {
159
174
  const context = 'leave_waitlist';
160
- this.toggleActionBarState(context);
175
+ this.ActionError(context);
161
176
 
162
177
  ActionsHandlerService({
163
178
  action: context,
@@ -166,12 +181,31 @@ export default class ActionsHandler extends LitElement {
166
181
  URLHelper.goToUrl(URLHelper.getRedirectUrl(), true);
167
182
  },
168
183
  error: data => {
169
- alert(data.error);
170
- this.toggleActionBarState(context);
184
+ this.ActionError(context, data);
185
+ },
186
+ });
187
+ }
188
+
189
+ handleLoanTokenPoller() {
190
+ const context = 'create_token';
191
+ ActionsHandlerService({
192
+ identifier: this.identifier,
193
+ action: context,
194
+ error: data => {
195
+ this.ActionError(context, data);
196
+ clearInterval(this.loanTokenInterval); // stop token fetch api
171
197
  },
172
198
  });
173
199
  }
174
200
 
201
+ ActionError(context, data = {}) {
202
+ this.dispatchEvent(
203
+ new CustomEvent('lendingActionError', {
204
+ detail: { context, data },
205
+ })
206
+ );
207
+ }
208
+
175
209
  handleLoginOk() {
176
210
  /* eslint class-methods-use-this: "off" */
177
211
  const target = `/account/login?referer=${encodeURIComponent(
@@ -183,9 +217,6 @@ export default class ActionsHandler extends LitElement {
183
217
  handleReadItNow(extraParam) {
184
218
  const currentParams = new URLSearchParams(window.location.search);
185
219
 
186
- // Delete the ?q= parameter just after borrow a book. see: WEBDEV-3979
187
- currentParams.delete('q');
188
-
189
220
  if (extraParam) {
190
221
  // append extraParam key-value in currentParams
191
222
  const extraParams = new URLSearchParams(extraParam);
@@ -221,12 +252,4 @@ export default class ActionsHandler extends LitElement {
221
252
  cookie += '; path=/; domain=.archive.org;';
222
253
  document.cookie = cookie;
223
254
  }
224
-
225
- toggleActionBarState(context, data = {}) {
226
- this.dispatchEvent(
227
- new CustomEvent('toggle-action-bar-state', {
228
- detail: { context, data },
229
- })
230
- );
231
- }
232
255
  }
@@ -7,6 +7,7 @@ import './components/collapsible-action-group.js';
7
7
  import './components/book-title-bar.js';
8
8
  import './components/text-group.js';
9
9
  import './components/info-icon.js';
10
+ import './components/dialog-alert.js';
10
11
 
11
12
  import GetLendingActions from './core/services/get-lending-actions.js';
12
13
  import { mobileContainerWidth } from './core/config/constants.js';
@@ -22,7 +23,9 @@ export default class IABookActions extends LitElement {
22
23
  bwbPurchaseUrl: { type: String },
23
24
  barType: { type: String },
24
25
  sharedObserver: { attribute: false },
25
- disable: { type: Boolean },
26
+ disableActionGroup: { type: Boolean },
27
+ dialogVisible: { type: Boolean },
28
+ bookHasBrowsed: { type: Boolean },
26
29
  };
27
30
  }
28
31
 
@@ -41,7 +44,9 @@ export default class IABookActions extends LitElement {
41
44
  this.primaryColor = 'primary';
42
45
  this.secondaryActions = [];
43
46
  this.lendingOptions = {};
44
- this.disable = false;
47
+ this.disableActionGroup = false;
48
+ this.dialogVisible = false;
49
+ this.bookHasBrowsed = false;
45
50
  }
46
51
 
47
52
  disconnectedCallback() {
@@ -141,10 +146,12 @@ export default class IABookActions extends LitElement {
141
146
  return action != null;
142
147
  });
143
148
 
144
- if (
149
+ this.bookHasBrowsed =
145
150
  this.lendingStatus.user_has_browsed &&
146
- !this.lendingStatus.browseHasExpired
147
- ) {
151
+ !this.lendingStatus.browsingExpired;
152
+ if (this.bookHasBrowsed) {
153
+ // start timer for 1-hour borrowed book.
154
+ // when 1 hour is completed, we shows browse-again button
148
155
  this.startBrowseTimer();
149
156
  }
150
157
  }
@@ -154,9 +161,22 @@ export default class IABookActions extends LitElement {
154
161
  <section class="lending-wrapper">
155
162
  ${this.barType === 'title' ? this.bookTitleBar : this.bookActionBar}
156
163
  </section>
164
+
165
+ <show-dialog
166
+ .title=${this.dialogTitle}
167
+ .body=${this.dialogBody}
168
+ .actions=${this.dialogActions}
169
+ ?opened=${this.dialogVisible}
170
+ ?allowClose=${this.allowClose}
171
+ @dialogAlertClose="${() => this.closeDialogAlert()}"
172
+ ></show-dialog>
157
173
  `;
158
174
  }
159
175
 
176
+ closeDialogAlert() {
177
+ this.dialogVisible = false;
178
+ }
179
+
160
180
  get bookTitleBar() {
161
181
  return html`<book-title-bar
162
182
  .identifier=${this.identifier}
@@ -173,29 +193,60 @@ export default class IABookActions extends LitElement {
173
193
  .primaryActions=${this.primaryActions}
174
194
  .secondaryActions=${this.secondaryActions}
175
195
  .width=${this.width}
176
- .hasAdminAccess=${this.hasAdminAccess}
177
- .disabled=${this.disable}
178
- @toggle-action-bar-state=${this.toggleActionBarState}
196
+ ?hasAdminAccess=${this.hasAdminAccess}
197
+ ?disabled=${this.disableActionGroup}
198
+ ?bookHasBrowsed=${this.bookHasBrowsed}
199
+ @lendingActionError=${this.handleLendingActionError}
179
200
  >
180
201
  </collapsible-action-group>
181
202
  ${this.textGroupTemplate} ${this.infoIconTemplate}
182
203
  `;
183
204
  }
184
205
 
185
- toggleActionBarState(e) {
206
+ handleLendingActionError(e) {
186
207
  // toggle activity loader
187
- this.disable = !this.disable;
188
-
189
- // update action bar states is book is not available to browse or borrow.
190
- if (e?.detail?.data?.error) {
191
- const errorMsg = e.detail.data.error;
192
- if (errorMsg.match(/not available to browse/gm)) {
193
- const currStatus = {...this.lendingStatus, available_to_browse: false };
194
- this.lendingStatus = currStatus;
195
- } else if (errorMsg.match(/not available to borrow/gm)) {
196
- const currStatus = { ...this.lendingStatus, available_to_borrow: false };
197
- this.lendingStatus = currStatus;
208
+ this.disableActionGroup = !this.disableActionGroup;
209
+
210
+ const context = e?.detail?.context;
211
+ const errorMsg = e?.detail?.data?.error;
212
+
213
+ // initiate dialog-alert if error occured
214
+ if (errorMsg) this.initializeDialogAlert(context, errorMsg);
215
+
216
+ // update action bar state if book is not available to browse or borrow.
217
+ if (errorMsg && errorMsg.match(/not available to borrow/gm)) {
218
+ let currStatus = this.lendingStatus;
219
+ if (context === 'browse_book') {
220
+ currStatus = {
221
+ ...this.lendingStatus,
222
+ available_to_browse: false,
223
+ };
224
+ } else if (context === 'borrow_book') {
225
+ currStatus = {
226
+ ...this.lendingStatus,
227
+ available_to_borrow: false,
228
+ };
198
229
  }
230
+ this.lendingStatus = currStatus;
231
+ }
232
+ }
233
+
234
+ /* set props value to show dialog alert */
235
+ initializeDialogAlert(context, errorMsg) {
236
+ this.dialogVisible = true;
237
+ this.dialogTitle = 'Sorry!';
238
+ this.dialogBody = errorMsg;
239
+ this.allowClose = true;
240
+
241
+ if (context === 'create_token') {
242
+ this.allowClose = false;
243
+ this.dialogActions = [
244
+ {
245
+ className: 'ia-button primary',
246
+ text: 'Back to Item Details',
247
+ href: this.lendingStatus.bookUrl,
248
+ },
249
+ ];
199
250
  }
200
251
  }
201
252
 
@@ -0,0 +1,62 @@
1
+ /* eslint-disable */
2
+ import { html, fixture, expect } from '@open-wc/testing';
3
+ import '../../src/components/dialog-alert.js';
4
+
5
+ const container = ({
6
+ title = '',
7
+ body = '',
8
+ allowClose = false,
9
+ opened = false,
10
+ } = {}) =>
11
+ html`<show-dialog
12
+ .title=${title}
13
+ .body=${body}
14
+ ?allowClose=${allowClose}
15
+ ?opened=${opened}
16
+ ></show-dialog>`;
17
+
18
+ describe('<show-dialog>', () => {
19
+ it('assign values and initiate dialog-alert', async () => {
20
+ const el = await fixture(
21
+ container({
22
+ title: 'internal error!',
23
+ body: 'there was an internal error',
24
+ opened: true,
25
+ })
26
+ );
27
+ expect(el.title).to.be.equal('internal error!');
28
+ expect(el.body).to.be.equal('there was an internal error');
29
+ });
30
+
31
+ it('close button should not render', async () => {
32
+ const el = await fixture(
33
+ container({
34
+ title: 'internal error!',
35
+ body: 'there was an internal error',
36
+ })
37
+ );
38
+ expect(el.title).to.be.equal('internal error!');
39
+ expect(el.body).to.be.equal('there was an internal error');
40
+
41
+ // close icon is not exist
42
+ const closeButton = el.shadowRoot.querySelector('ia-icon-close');
43
+ expect(closeButton).to.be.null;
44
+ });
45
+
46
+ it('close dialog-alert on X icon click', async () => {
47
+ const el = await fixture(
48
+ container({
49
+ title: 'internal error!',
50
+ body: 'there was an internal error',
51
+ allowClose: true,
52
+ opened: true,
53
+ })
54
+ );
55
+
56
+ const closeButton = el.shadowRoot.querySelector('ia-icon-close');
57
+ const errorEvent = new CustomEvent('dialogAlertClose');
58
+ closeButton.dispatchEvent(errorEvent);
59
+ await el.updateComplete;
60
+ expect(el.opened).to.be.true;
61
+ });
62
+ });
@@ -106,6 +106,64 @@ describe('Primary Actions data', () => {
106
106
  });
107
107
 
108
108
  describe('Borrow status actions', () => {
109
+ it('Update available_to_browse key when book is not available to borrow', async () => {
110
+ const el = await fixture(
111
+ container({
112
+ userid: '@user1',
113
+ lendingStatus: {
114
+ is_lendable: true,
115
+ available_to_browse: true,
116
+ available_to_borrow: true,
117
+ },
118
+ })
119
+ );
120
+
121
+ const errorEvent = new CustomEvent('lendingActionError');
122
+ el.addEventListener('lendingActionError', () => {
123
+ el.handleLendingActionError({
124
+ detail: {
125
+ context: 'browse_book',
126
+ data: { error: 'not available to borrow' },
127
+ },
128
+ });
129
+ });
130
+ el.dispatchEvent(errorEvent);
131
+ await el.updateComplete;
132
+
133
+ // removed available_to_browse from lending bar
134
+ expect(el.lendingStatus.available_to_browse).to.be.false;
135
+ expect(el.primaryActions[0].text).to.equal('Borrow for 14 days');
136
+ });
137
+
138
+ it('Update available_to_borrow key when book is not available to borrow', async () => {
139
+ const el = await fixture(
140
+ container({
141
+ userid: '@user1',
142
+ lendingStatus: {
143
+ is_lendable: true,
144
+ available_to_browse: true,
145
+ available_to_borrow: true,
146
+ },
147
+ })
148
+ );
149
+
150
+ const errorEvent = new Event('lendingActionError');
151
+ el.addEventListener('lendingActionError', () => {
152
+ el.handleLendingActionError({
153
+ detail: {
154
+ context: 'borrow_book',
155
+ data: { error: 'not available to borrow' },
156
+ },
157
+ });
158
+ });
159
+ el.dispatchEvent(errorEvent);
160
+ await el.updateComplete;
161
+
162
+ // removed available_to_borrow from lending bar
163
+ expect(el.lendingStatus.available_to_borrow).to.be.false;
164
+ expect(el.primaryActions[0].text).to.equal('Borrow for 1 hour');
165
+ });
166
+
109
167
  it('Check action for borrowable book without user', async () => {
110
168
  const el = await fixture(
111
169
  container({