@internetarchive/ia-book-actions 1.4.2 → 1.4.3-alpha1
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/index.html +397 -118
- package/package.json +2 -3
- package/src/assets/data/info.js +12 -0
- package/src/core/services/actions-handler/actions-handler-service.js +5 -1
- package/src/core/services/actions-handler/actions-handler.js +4 -3
- package/src/core/services/loan-analytics.js +1 -1
- package/src/core/services/loan-renew-helper.js +2 -3
- package/src/ia-book-actions.js +222 -221
- package/test/core/services/loan-renew-helper.test.js +1 -1
- package/test/ia-book-actions.test.js +221 -7
- package/.claude/settings.local.json +0 -16
|
@@ -82,7 +82,7 @@ describe('<ia-book-actions>', () => {
|
|
|
82
82
|
expect(el.disableActionGroup).to.be.true;
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
it('handles `BookReader:userAction` event', async () => {
|
|
85
|
+
it('handles `BookReader:userAction` event when loan is active', async () => {
|
|
86
86
|
const el = await fixture(
|
|
87
87
|
container({
|
|
88
88
|
userid: '@user1',
|
|
@@ -91,8 +91,10 @@ describe('<ia-book-actions>', () => {
|
|
|
91
91
|
);
|
|
92
92
|
|
|
93
93
|
const spy = Sinon.spy(el, 'autoLoanRenewChecker');
|
|
94
|
-
el.
|
|
94
|
+
el.lendingStatus = { ...el.lendingStatus, browsingExpired: false };
|
|
95
95
|
await el.updateComplete;
|
|
96
|
+
// Set borrowType after the update so setupLendingToolbarActions() doesn't overwrite it
|
|
97
|
+
el.borrowType = 'browsed';
|
|
96
98
|
|
|
97
99
|
window.dispatchEvent(new Event('BookReader:userAction'));
|
|
98
100
|
await el.updateComplete;
|
|
@@ -100,6 +102,25 @@ describe('<ia-book-actions>', () => {
|
|
|
100
102
|
expect(spy.calledOnce).to.be.true;
|
|
101
103
|
expect(spy.calledWith(true)).to.be.true;
|
|
102
104
|
});
|
|
105
|
+
|
|
106
|
+
it('handles `BookReader:userAction` event when loan has expired', async () => {
|
|
107
|
+
const el = await fixture(
|
|
108
|
+
container({
|
|
109
|
+
userid: '@user1',
|
|
110
|
+
identifier: 'foobar',
|
|
111
|
+
lendingStatus: { user_has_browsed: true, browsingExpired: true },
|
|
112
|
+
})
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const spy = Sinon.spy(el, 'autoRenewExpiredLoan');
|
|
116
|
+
el.borrowType = 'browsed';
|
|
117
|
+
await el.updateComplete;
|
|
118
|
+
|
|
119
|
+
window.dispatchEvent(new Event('BookReader:userAction'));
|
|
120
|
+
await el.updateComplete;
|
|
121
|
+
|
|
122
|
+
expect(spy.calledOnce).to.be.true;
|
|
123
|
+
});
|
|
103
124
|
});
|
|
104
125
|
|
|
105
126
|
describe('Primary Actions data', () => {
|
|
@@ -328,7 +349,9 @@ describe('Browsing expired status', () => {
|
|
|
328
349
|
await aTimeout(1500); // wait for 1.5 second
|
|
329
350
|
await el.updateComplete;
|
|
330
351
|
|
|
331
|
-
|
|
352
|
+
// Auto-return must not visibly change the action bar — it should stay
|
|
353
|
+
// exactly as it looked while reading (see WEBDEV-8322).
|
|
354
|
+
expect(el.primaryActions[0].text).to.equal('Return now');
|
|
332
355
|
|
|
333
356
|
expect(el.timerCountdownEl).to.exist;
|
|
334
357
|
|
|
@@ -371,7 +394,9 @@ describe('Browsing expired status', () => {
|
|
|
371
394
|
await aTimeout(1500); // wait for 1.5 sec
|
|
372
395
|
|
|
373
396
|
expect(eventReceived).to.equal(true);
|
|
374
|
-
|
|
397
|
+
// Auto-return must not visibly change the action bar — it should stay
|
|
398
|
+
// exactly as it looked while reading (see WEBDEV-8322).
|
|
399
|
+
expect(el.primaryActions[0].text).to.equal('Return now');
|
|
375
400
|
expect(el.tokenPoller.loanTokenInterval).to.equal(undefined);
|
|
376
401
|
});
|
|
377
402
|
});
|
|
@@ -467,7 +492,7 @@ describe('Visibility change API for document', async () => {
|
|
|
467
492
|
});
|
|
468
493
|
});
|
|
469
494
|
|
|
470
|
-
it('when book is not expired', async () => {
|
|
495
|
+
it('when book is not expired and loan time is valid', async () => {
|
|
471
496
|
const el = await fixture(
|
|
472
497
|
container({
|
|
473
498
|
userid: '@user1',
|
|
@@ -480,31 +505,220 @@ describe('Visibility change API for document', async () => {
|
|
|
480
505
|
);
|
|
481
506
|
await el.updateComplete;
|
|
482
507
|
|
|
508
|
+
// Set a valid loan time well into the future
|
|
509
|
+
await el.localCache.set({
|
|
510
|
+
key: 'foobar-loanTime',
|
|
511
|
+
value: new Date(new Date().getTime() + 3600 * 1000),
|
|
512
|
+
ttl: 3600,
|
|
513
|
+
});
|
|
514
|
+
|
|
483
515
|
const spy = Sinon.spy(el, 'loanStatusCheckInterval');
|
|
484
516
|
document.dispatchEvent(new Event('visibilitychange'));
|
|
517
|
+
await aTimeout(100);
|
|
518
|
+
|
|
519
|
+
expect(spy.calledOnce).to.be.true;
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it('when loan expired while tab was hidden (browsingExpired false, loanTime past)', async () => {
|
|
523
|
+
const el = await fixture(
|
|
524
|
+
container({
|
|
525
|
+
userid: '@user1',
|
|
526
|
+
identifier: 'foobar',
|
|
527
|
+
lendingStatus: {
|
|
528
|
+
user_has_browsed: true,
|
|
529
|
+
browsingExpired: false,
|
|
530
|
+
},
|
|
531
|
+
})
|
|
532
|
+
);
|
|
533
|
+
await el.updateComplete;
|
|
534
|
+
|
|
535
|
+
// Set a loan time already in the past
|
|
536
|
+
await el.localCache.set({
|
|
537
|
+
key: 'foobar-loanTime',
|
|
538
|
+
value: new Date(new Date().getTime() - 10 * 1000),
|
|
539
|
+
ttl: 3600,
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
const spy = Sinon.spy(el, 'autoRenewExpiredLoan');
|
|
543
|
+
document.dispatchEvent(new Event('visibilitychange'));
|
|
544
|
+
await aTimeout(100);
|
|
485
545
|
|
|
486
546
|
expect(spy.calledOnce).to.be.true;
|
|
487
547
|
});
|
|
488
548
|
|
|
489
|
-
it('when
|
|
549
|
+
it('when loan already marked expired (browsingExpired true)', async () => {
|
|
490
550
|
const el = await fixture(
|
|
491
551
|
container({
|
|
492
552
|
userid: '@user1',
|
|
493
553
|
identifier: 'foobar',
|
|
494
554
|
lendingStatus: {
|
|
495
555
|
user_has_browsed: true,
|
|
556
|
+
browsingExpired: true,
|
|
496
557
|
},
|
|
497
558
|
})
|
|
498
559
|
);
|
|
499
560
|
await el.updateComplete;
|
|
500
561
|
|
|
501
|
-
|
|
562
|
+
el.borrowType = 'browsed';
|
|
563
|
+
|
|
564
|
+
const spy = Sinon.spy(el, 'autoRenewExpiredLoan');
|
|
502
565
|
document.dispatchEvent(new Event('visibilitychange'));
|
|
566
|
+
await aTimeout(100);
|
|
503
567
|
|
|
504
568
|
expect(spy.calledOnce).to.be.true;
|
|
505
569
|
});
|
|
506
570
|
});
|
|
507
571
|
|
|
572
|
+
describe('autoRenewExpiredLoan', () => {
|
|
573
|
+
it('optimistically resets browsingExpired so the button stays red', async () => {
|
|
574
|
+
const el = await fixture(
|
|
575
|
+
container({
|
|
576
|
+
userid: '@user1',
|
|
577
|
+
identifier: 'foobar',
|
|
578
|
+
lendingStatus: {
|
|
579
|
+
user_has_browsed: true,
|
|
580
|
+
available_to_browse: true,
|
|
581
|
+
browsingExpired: true,
|
|
582
|
+
},
|
|
583
|
+
})
|
|
584
|
+
);
|
|
585
|
+
await el.updateComplete;
|
|
586
|
+
|
|
587
|
+
// Before: browsingExpired true → borrow1HrAction → primary color is 'primary' (blue)
|
|
588
|
+
expect(el.primaryColor).to.equal('primary');
|
|
589
|
+
|
|
590
|
+
el.autoRenewExpiredLoan();
|
|
591
|
+
await el.updateComplete;
|
|
592
|
+
|
|
593
|
+
// After: browsingExpired flipped to false → patronIsReadingAction → 'danger' (red)
|
|
594
|
+
expect(el.lendingStatus.browsingExpired).to.be.false;
|
|
595
|
+
expect(el.primaryColor).to.equal('danger');
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it('sets loanRenewInProgress and triggers renewNow after timeout', async () => {
|
|
599
|
+
const el = await fixture(
|
|
600
|
+
container({
|
|
601
|
+
userid: '@user1',
|
|
602
|
+
identifier: 'foobar',
|
|
603
|
+
lendingStatus: {
|
|
604
|
+
user_has_browsed: true,
|
|
605
|
+
browsingExpired: true,
|
|
606
|
+
},
|
|
607
|
+
})
|
|
608
|
+
);
|
|
609
|
+
await el.updateComplete;
|
|
610
|
+
|
|
611
|
+
el.autoRenewExpiredLoan();
|
|
612
|
+
|
|
613
|
+
expect(el.loanRenewInProgress).to.be.true;
|
|
614
|
+
|
|
615
|
+
// renewNow is set in a setTimeout — flush the macrotask queue
|
|
616
|
+
await aTimeout(50);
|
|
617
|
+
expect(el.loanRenewResult.renewNow).to.be.true;
|
|
618
|
+
expect(el.loanRenewResult.renewType).to.equal('auto');
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it('ignores subsequent calls while a renewal is already in progress', async () => {
|
|
622
|
+
const el = await fixture(
|
|
623
|
+
container({
|
|
624
|
+
userid: '@user1',
|
|
625
|
+
identifier: 'foobar',
|
|
626
|
+
lendingStatus: {
|
|
627
|
+
user_has_browsed: true,
|
|
628
|
+
browsingExpired: true,
|
|
629
|
+
},
|
|
630
|
+
})
|
|
631
|
+
);
|
|
632
|
+
await el.updateComplete;
|
|
633
|
+
|
|
634
|
+
// Spy *after* fixture so the handler is the same instance
|
|
635
|
+
const spy = Sinon.spy(el, 'autoRenewExpiredLoan');
|
|
636
|
+
|
|
637
|
+
el.autoRenewExpiredLoan(); // first call — proceeds
|
|
638
|
+
el.autoRenewExpiredLoan(); // second call — should be a no-op
|
|
639
|
+
|
|
640
|
+
// The spy counts both invocations but the internal guard stops the second
|
|
641
|
+
expect(spy.callCount).to.equal(2);
|
|
642
|
+
// loanRenewInProgress is still true (not double-set or cleared)
|
|
643
|
+
expect(el.loanRenewInProgress).to.be.true;
|
|
644
|
+
|
|
645
|
+
// Only one setTimeout fires (the second call returned early)
|
|
646
|
+
await aTimeout(50);
|
|
647
|
+
expect(el.loanRenewResult.renewNow).to.be.true;
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
it('clears loanRenewInProgress after a successful loanAutoRenewed event', async () => {
|
|
651
|
+
const el = await fixture(
|
|
652
|
+
container({
|
|
653
|
+
userid: '@user1',
|
|
654
|
+
identifier: 'foobar',
|
|
655
|
+
lendingStatus: {
|
|
656
|
+
user_has_browsed: true,
|
|
657
|
+
browsingExpired: false,
|
|
658
|
+
secondsLeftOnLoan: 100,
|
|
659
|
+
},
|
|
660
|
+
})
|
|
661
|
+
);
|
|
662
|
+
await el.updateComplete;
|
|
663
|
+
|
|
664
|
+
await el.localCache.set({
|
|
665
|
+
key: 'foobar-loanTime',
|
|
666
|
+
value: new Date(new Date().getTime() + 3600 * 1000),
|
|
667
|
+
ttl: 3600,
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
el.autoRenewExpiredLoan();
|
|
671
|
+
expect(el.loanRenewInProgress).to.be.true;
|
|
672
|
+
|
|
673
|
+
// Wait for the deferred renewNow to be set
|
|
674
|
+
await aTimeout(50);
|
|
675
|
+
|
|
676
|
+
// Simulate the renew_loan success response
|
|
677
|
+
const collapsibleActionGroupEl = el.shadowRoot.querySelector(
|
|
678
|
+
'collapsible-action-group'
|
|
679
|
+
);
|
|
680
|
+
collapsibleActionGroupEl.dispatchEvent(
|
|
681
|
+
new CustomEvent('loanAutoRenewed', {
|
|
682
|
+
detail: { data: { loan: { identifier: 'foobar' } } },
|
|
683
|
+
})
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
await aTimeout(100);
|
|
687
|
+
await el.updateComplete;
|
|
688
|
+
|
|
689
|
+
expect(el.loanRenewInProgress).to.be.false;
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
it('clears loanRenewInProgress and shows unavailable modal on renew_loan failure', async () => {
|
|
693
|
+
const el = await fixture(
|
|
694
|
+
container({
|
|
695
|
+
userid: '@user1',
|
|
696
|
+
identifier: 'foobar',
|
|
697
|
+
lendingStatus: {
|
|
698
|
+
user_has_browsed: true,
|
|
699
|
+
browsingExpired: true,
|
|
700
|
+
},
|
|
701
|
+
})
|
|
702
|
+
);
|
|
703
|
+
await el.updateComplete;
|
|
704
|
+
|
|
705
|
+
const showModalSpy = Sinon.spy(el, 'showLoanUnavailableModal');
|
|
706
|
+
|
|
707
|
+
el.autoRenewExpiredLoan();
|
|
708
|
+
expect(el.loanRenewInProgress).to.be.true;
|
|
709
|
+
|
|
710
|
+
el.handleLendingActionError({
|
|
711
|
+
detail: {
|
|
712
|
+
action: 'renew_loan',
|
|
713
|
+
data: { error: 'book is not available' },
|
|
714
|
+
},
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
expect(el.loanRenewInProgress).to.be.false;
|
|
718
|
+
expect(showModalSpy.calledOnce).to.be.true;
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
|
|
508
722
|
describe('Shared Resize Observer', () => {
|
|
509
723
|
it('can receive a Shared Resize Observer', async () => {
|
|
510
724
|
const sharedObserverStub = new SharedResizeObserver();
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(yarn outdated *)",
|
|
5
|
-
"Bash(gh pr *)",
|
|
6
|
-
"Bash(git log *)",
|
|
7
|
-
"mcp__plugin_atlassian_atlassian__getAccessibleAtlassianResources",
|
|
8
|
-
"Bash(npm view *)",
|
|
9
|
-
"mcp__plugin_atlassian_atlassian__searchJiraIssuesUsingJql",
|
|
10
|
-
"Bash(xargs -I {} basename {})",
|
|
11
|
-
"Bash(npm info *)",
|
|
12
|
-
"mcp__plugin_atlassian_atlassian__getJiraIssue",
|
|
13
|
-
"mcp__plugin_atlassian_atlassian__getJiraProjectIssueTypesMetadata"
|
|
14
|
-
]
|
|
15
|
-
}
|
|
16
|
-
}
|