decap-cms-backend-github 3.7.0 → 3.8.0
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/CHANGELOG.md +4 -0
- package/dist/decap-cms-backend-github.js +73 -73
- package/dist/decap-cms-backend-github.js.LICENSE.txt +18 -8
- package/dist/decap-cms-backend-github.js.map +1 -1
- package/dist/esm/API.js +414 -0
- package/dist/esm/AuthenticationPage.js +23 -14
- package/dist/esm/implementation.js +176 -7
- package/package.json +2 -2
- package/src/API.ts +485 -0
- package/src/__tests__/implementation.spec.js +487 -0
- package/src/implementation.tsx +203 -3
- package/src/polling.ts +472 -0
|
@@ -13,6 +13,17 @@ describe('github backend implementation', () => {
|
|
|
13
13
|
},
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
const configWithNotes = {
|
|
17
|
+
backend: {
|
|
18
|
+
repo: 'owner/repo',
|
|
19
|
+
open_authoring: false,
|
|
20
|
+
api_root: 'https://api.github.com',
|
|
21
|
+
},
|
|
22
|
+
editor: {
|
|
23
|
+
notes: true,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
16
27
|
const createObjectURL = jest.fn();
|
|
17
28
|
global.URL = {
|
|
18
29
|
createObjectURL,
|
|
@@ -358,4 +369,480 @@ describe('github backend implementation', () => {
|
|
|
358
369
|
});
|
|
359
370
|
});
|
|
360
371
|
});
|
|
372
|
+
describe('notes implementation', () => {
|
|
373
|
+
const mockAPI = {
|
|
374
|
+
getEntryNotes: jest.fn(),
|
|
375
|
+
addNoteToEntry: jest.fn(),
|
|
376
|
+
updateEntryNote: jest.fn(),
|
|
377
|
+
deleteEntryNote: jest.fn(),
|
|
378
|
+
closeEntryNotesIssue: jest.fn(),
|
|
379
|
+
closeIssueOnPublish: jest.fn(),
|
|
380
|
+
reopenIssueOnUnpublish: jest.fn(),
|
|
381
|
+
readFile: jest.fn(),
|
|
382
|
+
deleteUnpublishedEntry: jest.fn().mockResolvedValue(undefined),
|
|
383
|
+
publishUnpublishedEntry: jest.fn().mockResolvedValue(undefined),
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
beforeEach(() => {
|
|
387
|
+
jest.clearAllMocks();
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
describe('getNotes', () => {
|
|
391
|
+
it('should retrieve notes for an entry', async () => {
|
|
392
|
+
const gitHubImplementation = new GitHubImplementation(configWithNotes);
|
|
393
|
+
gitHubImplementation.api = mockAPI;
|
|
394
|
+
|
|
395
|
+
const mockNotes = [
|
|
396
|
+
{
|
|
397
|
+
id: '1',
|
|
398
|
+
author: 'user1',
|
|
399
|
+
avatarUrl: 'https://avatar.url',
|
|
400
|
+
text: 'Test note',
|
|
401
|
+
timestamp: '2025-01-01T00:00:00Z',
|
|
402
|
+
resolved: false,
|
|
403
|
+
},
|
|
404
|
+
];
|
|
405
|
+
|
|
406
|
+
mockAPI.getEntryNotes.mockResolvedValue(mockNotes);
|
|
407
|
+
|
|
408
|
+
const result = await gitHubImplementation.getNotes('posts', 'my-post');
|
|
409
|
+
|
|
410
|
+
expect(result).toEqual([
|
|
411
|
+
{
|
|
412
|
+
...mockNotes[0],
|
|
413
|
+
entrySlug: 'my-post',
|
|
414
|
+
},
|
|
415
|
+
]);
|
|
416
|
+
expect(mockAPI.getEntryNotes).toHaveBeenCalledWith('posts', 'my-post');
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('should return empty array on error', async () => {
|
|
420
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
421
|
+
gitHubImplementation.api = mockAPI;
|
|
422
|
+
|
|
423
|
+
mockAPI.getEntryNotes.mockRejectedValue(new Error('API Error'));
|
|
424
|
+
|
|
425
|
+
const result = await gitHubImplementation.getNotes('posts', 'my-post');
|
|
426
|
+
|
|
427
|
+
expect(result).toEqual([]);
|
|
428
|
+
expect(console.error).toHaveBeenCalledWith('Failed to get notes:', expect.any(Error));
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
describe('addNote', () => {
|
|
433
|
+
it('should add a note to an entry', async () => {
|
|
434
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
435
|
+
gitHubImplementation.api = mockAPI;
|
|
436
|
+
gitHubImplementation.token = 'test-token';
|
|
437
|
+
gitHubImplementation.currentUser = jest.fn().mockResolvedValue({
|
|
438
|
+
login: 'testuser',
|
|
439
|
+
name: 'Test User',
|
|
440
|
+
avatar_url: 'https://avatar.url',
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
const noteData = {
|
|
444
|
+
text: 'New note',
|
|
445
|
+
timestamp: '2025-01-01T00:00:00Z',
|
|
446
|
+
resolved: false,
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
mockAPI.addNoteToEntry.mockResolvedValue({
|
|
450
|
+
commentId: 'comment-123',
|
|
451
|
+
issueUrl: 'https://github.com/owner/repo/issues/1',
|
|
452
|
+
});
|
|
453
|
+
mockAPI.readFile.mockResolvedValue('title: My Post Title\n\nContent');
|
|
454
|
+
|
|
455
|
+
const result = await gitHubImplementation.addNote('posts', 'my-post', noteData);
|
|
456
|
+
|
|
457
|
+
expect(result).toMatchObject({
|
|
458
|
+
text: 'New note',
|
|
459
|
+
author: 'testuser',
|
|
460
|
+
avatarUrl: 'https://avatar.url',
|
|
461
|
+
entrySlug: 'my-post',
|
|
462
|
+
resolved: false,
|
|
463
|
+
id: 'comment-123',
|
|
464
|
+
});
|
|
465
|
+
expect(mockAPI.addNoteToEntry).toHaveBeenCalledWith(
|
|
466
|
+
'posts',
|
|
467
|
+
'my-post',
|
|
468
|
+
expect.objectContaining({
|
|
469
|
+
text: 'New note',
|
|
470
|
+
author: 'testuser',
|
|
471
|
+
}),
|
|
472
|
+
'My Post Title',
|
|
473
|
+
);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('should handle missing entry title gracefully', async () => {
|
|
477
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
478
|
+
gitHubImplementation.api = mockAPI;
|
|
479
|
+
gitHubImplementation.token = 'test-token';
|
|
480
|
+
gitHubImplementation.currentUser = jest.fn().mockResolvedValue({
|
|
481
|
+
login: 'testuser',
|
|
482
|
+
avatar_url: 'https://avatar.url',
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
const noteData = {
|
|
486
|
+
text: 'New note',
|
|
487
|
+
timestamp: '2025-01-01T00:00:00Z',
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
mockAPI.addNoteToEntry.mockResolvedValue({
|
|
491
|
+
commentId: 'comment-123',
|
|
492
|
+
issueUrl: 'https://github.com/owner/repo/issues/1',
|
|
493
|
+
});
|
|
494
|
+
mockAPI.readFile.mockRejectedValue(new Error('Not found'));
|
|
495
|
+
|
|
496
|
+
const result = await gitHubImplementation.addNote('posts', 'my-post', noteData);
|
|
497
|
+
|
|
498
|
+
expect(mockAPI.addNoteToEntry).toHaveBeenCalledWith(
|
|
499
|
+
'posts',
|
|
500
|
+
'my-post',
|
|
501
|
+
expect.any(Object),
|
|
502
|
+
undefined,
|
|
503
|
+
);
|
|
504
|
+
expect(result.id).toBe('comment-123');
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
describe('updateNote', () => {
|
|
509
|
+
it('should update an existing note', async () => {
|
|
510
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
511
|
+
gitHubImplementation.api = mockAPI;
|
|
512
|
+
|
|
513
|
+
const existingNotes = [
|
|
514
|
+
{
|
|
515
|
+
id: 'note-1',
|
|
516
|
+
author: 'user1',
|
|
517
|
+
avatarUrl: 'https://avatar.url',
|
|
518
|
+
text: 'Original text',
|
|
519
|
+
timestamp: '2025-01-01T00:00:00Z',
|
|
520
|
+
resolved: false,
|
|
521
|
+
entrySlug: 'my-post',
|
|
522
|
+
},
|
|
523
|
+
];
|
|
524
|
+
|
|
525
|
+
mockAPI.getEntryNotes.mockResolvedValue(existingNotes);
|
|
526
|
+
mockAPI.updateEntryNote.mockResolvedValue(undefined);
|
|
527
|
+
|
|
528
|
+
const updates = { text: 'Updated text', resolved: true };
|
|
529
|
+
const result = await gitHubImplementation.updateNote('posts', 'my-post', 'note-1', updates);
|
|
530
|
+
|
|
531
|
+
expect(result).toEqual({
|
|
532
|
+
...existingNotes[0],
|
|
533
|
+
text: 'Updated text',
|
|
534
|
+
resolved: true,
|
|
535
|
+
});
|
|
536
|
+
expect(mockAPI.updateEntryNote).toHaveBeenCalledWith('note-1', result);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('should throw error if note not found', async () => {
|
|
540
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
541
|
+
gitHubImplementation.api = mockAPI;
|
|
542
|
+
|
|
543
|
+
mockAPI.getEntryNotes.mockResolvedValue([]);
|
|
544
|
+
|
|
545
|
+
await expect(
|
|
546
|
+
gitHubImplementation.updateNote('posts', 'my-post', 'non-existent', {}),
|
|
547
|
+
).rejects.toThrow('Note with ID non-existent not found');
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
describe('deleteNote', () => {
|
|
552
|
+
it('should delete an existing note', async () => {
|
|
553
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
554
|
+
gitHubImplementation.api = mockAPI;
|
|
555
|
+
|
|
556
|
+
const existingNotes = [
|
|
557
|
+
{
|
|
558
|
+
id: 'note-1',
|
|
559
|
+
author: 'user1',
|
|
560
|
+
text: 'Test note',
|
|
561
|
+
entrySlug: 'my-post',
|
|
562
|
+
},
|
|
563
|
+
];
|
|
564
|
+
|
|
565
|
+
mockAPI.getEntryNotes.mockResolvedValue(existingNotes);
|
|
566
|
+
mockAPI.deleteEntryNote.mockResolvedValue(undefined);
|
|
567
|
+
|
|
568
|
+
await gitHubImplementation.deleteNote('posts', 'my-post', 'note-1');
|
|
569
|
+
|
|
570
|
+
expect(mockAPI.deleteEntryNote).toHaveBeenCalledWith('note-1');
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it('should throw error if note not found', async () => {
|
|
574
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
575
|
+
gitHubImplementation.api = mockAPI;
|
|
576
|
+
|
|
577
|
+
mockAPI.getEntryNotes.mockResolvedValue([]);
|
|
578
|
+
|
|
579
|
+
await expect(
|
|
580
|
+
gitHubImplementation.deleteNote('posts', 'my-post', 'non-existent'),
|
|
581
|
+
).rejects.toThrow('Note with ID non-existent not found');
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
describe('toggleNoteResolution', () => {
|
|
586
|
+
it('should toggle note resolved status from false to true', async () => {
|
|
587
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
588
|
+
gitHubImplementation.api = mockAPI;
|
|
589
|
+
|
|
590
|
+
const existingNotes = [
|
|
591
|
+
{
|
|
592
|
+
id: 'note-1',
|
|
593
|
+
author: 'user1',
|
|
594
|
+
avatarUrl: 'https://avatar.url',
|
|
595
|
+
text: 'Test note',
|
|
596
|
+
timestamp: '2025-01-01T00:00:00Z',
|
|
597
|
+
resolved: false,
|
|
598
|
+
entrySlug: 'my-post',
|
|
599
|
+
},
|
|
600
|
+
];
|
|
601
|
+
|
|
602
|
+
mockAPI.getEntryNotes.mockResolvedValue(existingNotes);
|
|
603
|
+
mockAPI.updateEntryNote.mockResolvedValue(undefined);
|
|
604
|
+
|
|
605
|
+
const result = await gitHubImplementation.toggleNoteResolution(
|
|
606
|
+
'posts',
|
|
607
|
+
'my-post',
|
|
608
|
+
'note-1',
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
expect(result.resolved).toBe(true);
|
|
612
|
+
expect(mockAPI.updateEntryNote).toHaveBeenCalledWith(
|
|
613
|
+
'note-1',
|
|
614
|
+
expect.objectContaining({ resolved: true }),
|
|
615
|
+
);
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it('should toggle note resolved status from true to false', async () => {
|
|
619
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
620
|
+
gitHubImplementation.api = mockAPI;
|
|
621
|
+
|
|
622
|
+
const existingNotes = [
|
|
623
|
+
{
|
|
624
|
+
id: 'note-1',
|
|
625
|
+
resolved: true,
|
|
626
|
+
entrySlug: 'my-post',
|
|
627
|
+
},
|
|
628
|
+
];
|
|
629
|
+
|
|
630
|
+
mockAPI.getEntryNotes.mockResolvedValue(existingNotes);
|
|
631
|
+
mockAPI.updateEntryNote.mockResolvedValue(undefined);
|
|
632
|
+
|
|
633
|
+
const result = await gitHubImplementation.toggleNoteResolution(
|
|
634
|
+
'posts',
|
|
635
|
+
'my-post',
|
|
636
|
+
'note-1',
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
expect(result.resolved).toBe(false);
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
it('should throw error if note not found', async () => {
|
|
643
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
644
|
+
gitHubImplementation.api = mockAPI;
|
|
645
|
+
|
|
646
|
+
mockAPI.getEntryNotes.mockResolvedValue([]);
|
|
647
|
+
|
|
648
|
+
await expect(
|
|
649
|
+
gitHubImplementation.toggleNoteResolution('posts', 'my-post', 'non-existent'),
|
|
650
|
+
).rejects.toThrow('Note with ID non-existent not found');
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
describe('reopenIssueForUnpublishedEntry', () => {
|
|
655
|
+
it('should reopen issue for unpublished entry', async () => {
|
|
656
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
657
|
+
gitHubImplementation.api = mockAPI;
|
|
658
|
+
|
|
659
|
+
mockAPI.reopenIssueOnUnpublish.mockResolvedValue(undefined);
|
|
660
|
+
|
|
661
|
+
await gitHubImplementation.reopenIssueForUnpublishedEntry('posts', 'my-post');
|
|
662
|
+
|
|
663
|
+
expect(mockAPI.reopenIssueOnUnpublish).toHaveBeenCalledWith('posts', 'my-post');
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
describe('deleteUnpublishedEntry with notes cleanup', () => {
|
|
668
|
+
it('should delete entry and close associated notes issue', async () => {
|
|
669
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
670
|
+
gitHubImplementation.api = mockAPI; // Just use mockAPI directly
|
|
671
|
+
|
|
672
|
+
await gitHubImplementation.deleteUnpublishedEntry('posts', 'my-post');
|
|
673
|
+
|
|
674
|
+
expect(mockAPI.deleteUnpublishedEntry).toHaveBeenCalledWith('posts', 'my-post');
|
|
675
|
+
expect(mockAPI.closeEntryNotesIssue).toHaveBeenCalledWith('posts', 'my-post');
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
it('should continue deletion even if closing issue fails', async () => {
|
|
679
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
680
|
+
gitHubImplementation.api = mockAPI;
|
|
681
|
+
mockAPI.closeEntryNotesIssue.mockRejectedValue(new Error('Issue close failed'));
|
|
682
|
+
|
|
683
|
+
await gitHubImplementation.deleteUnpublishedEntry('posts', 'my-post');
|
|
684
|
+
|
|
685
|
+
expect(mockAPI.deleteUnpublishedEntry).toHaveBeenCalledWith('posts', 'my-post');
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
describe('publishUnpublishedEntry with issue cleanup', () => {
|
|
690
|
+
it('should publish entry and close issue', async () => {
|
|
691
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
692
|
+
gitHubImplementation.api = mockAPI;
|
|
693
|
+
|
|
694
|
+
await gitHubImplementation.publishUnpublishedEntry('posts', 'my-post');
|
|
695
|
+
|
|
696
|
+
expect(mockAPI.publishUnpublishedEntry).toHaveBeenCalledWith('posts', 'my-post');
|
|
697
|
+
expect(mockAPI.closeIssueOnPublish).toHaveBeenCalledWith('posts', 'my-post');
|
|
698
|
+
});
|
|
699
|
+
});
|
|
700
|
+
describe('notes polling', () => {
|
|
701
|
+
beforeEach(() => {
|
|
702
|
+
jest.clearAllMocks();
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
it('should start notes polling', async () => {
|
|
706
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
707
|
+
gitHubImplementation.pollingManager = {
|
|
708
|
+
watchIssueWithRetry: jest.fn().mockResolvedValue(() => {}),
|
|
709
|
+
getStatus: jest.fn().mockReturnValue({ currentWatch: null }),
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
const callbacks = {
|
|
713
|
+
onUpdate: jest.fn(),
|
|
714
|
+
onChange: jest.fn(),
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
await gitHubImplementation.startNotesPolling('posts', 'my-post', callbacks);
|
|
718
|
+
|
|
719
|
+
expect(gitHubImplementation.pollingManager.watchIssueWithRetry).toHaveBeenCalledWith(
|
|
720
|
+
'posts',
|
|
721
|
+
'my-post',
|
|
722
|
+
callbacks,
|
|
723
|
+
5,
|
|
724
|
+
2000,
|
|
725
|
+
);
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
it('should not start polling if already watching same entry', async () => {
|
|
729
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
730
|
+
gitHubImplementation.pollingManager = {
|
|
731
|
+
watchIssueWithRetry: jest.fn().mockResolvedValue(() => {}),
|
|
732
|
+
getStatus: jest.fn().mockReturnValue({ currentWatch: 'posts/my-post' }),
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
const callbacks = { onUpdate: jest.fn() };
|
|
736
|
+
|
|
737
|
+
await gitHubImplementation.startNotesPolling('posts', 'my-post', callbacks);
|
|
738
|
+
|
|
739
|
+
expect(gitHubImplementation.pollingManager.watchIssueWithRetry).not.toHaveBeenCalled();
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
it('should stop notes polling', async () => {
|
|
743
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
744
|
+
const unwatchFn = jest.fn();
|
|
745
|
+
gitHubImplementation.unwatchFunctions.set('posts/my-post', unwatchFn);
|
|
746
|
+
|
|
747
|
+
await gitHubImplementation.stopNotesPolling('posts', 'my-post');
|
|
748
|
+
|
|
749
|
+
expect(unwatchFn).toHaveBeenCalledTimes(1);
|
|
750
|
+
expect(gitHubImplementation.unwatchFunctions.has('posts/my-post')).toBe(false);
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
it('should handle stopping polling when not active', async () => {
|
|
754
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
755
|
+
|
|
756
|
+
await expect(
|
|
757
|
+
gitHubImplementation.stopNotesPolling('posts', 'my-post'),
|
|
758
|
+
).resolves.not.toThrow();
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
it('should refresh notes immediately', async () => {
|
|
762
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
763
|
+
gitHubImplementation.pollingManager = {
|
|
764
|
+
checkIssueNow: jest.fn().mockResolvedValue(undefined),
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
await gitHubImplementation.refreshNotesNow('posts', 'my-post');
|
|
768
|
+
|
|
769
|
+
expect(gitHubImplementation.pollingManager.checkIssueNow).toHaveBeenCalledWith(
|
|
770
|
+
'posts',
|
|
771
|
+
'my-post',
|
|
772
|
+
);
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
it('should throw error when refreshing without polling manager', async () => {
|
|
776
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
777
|
+
gitHubImplementation.pollingManager = undefined;
|
|
778
|
+
|
|
779
|
+
await expect(gitHubImplementation.refreshNotesNow('posts', 'my-post')).rejects.toThrow(
|
|
780
|
+
'Polling manager not initialized',
|
|
781
|
+
);
|
|
782
|
+
});
|
|
783
|
+
it('should start polling and store unwatch function', async () => {
|
|
784
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
785
|
+
const unwatchFn = jest.fn();
|
|
786
|
+
|
|
787
|
+
gitHubImplementation.pollingManager = {
|
|
788
|
+
watchIssueWithRetry: jest.fn().mockResolvedValue(unwatchFn),
|
|
789
|
+
getStatus: jest.fn().mockReturnValue({ currentWatch: null }),
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
const callbacks = {
|
|
793
|
+
onUpdate: jest.fn(),
|
|
794
|
+
onChange: jest.fn(),
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
await gitHubImplementation.startNotesPolling('posts', 'my-post', callbacks);
|
|
798
|
+
|
|
799
|
+
expect(gitHubImplementation.pollingManager.watchIssueWithRetry).toHaveBeenCalledWith(
|
|
800
|
+
'posts',
|
|
801
|
+
'my-post',
|
|
802
|
+
callbacks,
|
|
803
|
+
5,
|
|
804
|
+
2000,
|
|
805
|
+
);
|
|
806
|
+
expect(gitHubImplementation.unwatchFunctions.get('posts/my-post')).toBe(unwatchFn);
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
it('should stop existing polling before starting new one', async () => {
|
|
810
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
811
|
+
const oldUnwatchFn = jest.fn();
|
|
812
|
+
const newUnwatchFn = jest.fn();
|
|
813
|
+
|
|
814
|
+
gitHubImplementation.unwatchFunctions.set('posts/my-post', oldUnwatchFn);
|
|
815
|
+
gitHubImplementation.pollingManager = {
|
|
816
|
+
watchIssueWithRetry: jest.fn().mockResolvedValue(newUnwatchFn),
|
|
817
|
+
getStatus: jest.fn().mockReturnValue({ currentWatch: 'posts/other-post' }),
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
const callbacks = { onUpdate: jest.fn() };
|
|
821
|
+
|
|
822
|
+
await gitHubImplementation.startNotesPolling('posts', 'my-post', callbacks);
|
|
823
|
+
|
|
824
|
+
expect(oldUnwatchFn).toHaveBeenCalledTimes(1);
|
|
825
|
+
expect(gitHubImplementation.unwatchFunctions.get('posts/my-post')).toBe(newUnwatchFn);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
it('should handle polling manager error gracefully', async () => {
|
|
829
|
+
const gitHubImplementation = new GitHubImplementation(config);
|
|
830
|
+
|
|
831
|
+
gitHubImplementation.pollingManager = {
|
|
832
|
+
watchIssueWithRetry: jest.fn().mockRejectedValue(new Error('Failed to find issue')),
|
|
833
|
+
getStatus: jest.fn().mockReturnValue({ currentWatch: null }),
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
const callbacks = { onUpdate: jest.fn() };
|
|
837
|
+
|
|
838
|
+
await gitHubImplementation.startNotesPolling('posts', 'my-post', callbacks);
|
|
839
|
+
|
|
840
|
+
// Should not throw, just log error
|
|
841
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
842
|
+
'[DecapNotes Polling] Failed to start polling after retries:',
|
|
843
|
+
expect.any(Error),
|
|
844
|
+
);
|
|
845
|
+
});
|
|
846
|
+
});
|
|
847
|
+
});
|
|
361
848
|
});
|