@theia/getting-started 1.76.0-next.16 → 1.76.0-next.18

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.
Files changed (36) hide show
  1. package/README.md +44 -0
  2. package/lib/browser/getting-started-frontend-module.d.ts.map +1 -1
  3. package/lib/browser/getting-started-frontend-module.js +3 -0
  4. package/lib/browser/getting-started-frontend-module.js.map +1 -1
  5. package/lib/browser/getting-started-widget.d.ts +0 -7
  6. package/lib/browser/getting-started-widget.d.ts.map +1 -1
  7. package/lib/browser/getting-started-widget.js +1 -29
  8. package/lib/browser/getting-started-widget.js.map +1 -1
  9. package/lib/browser/walkthrough-section.d.ts +2 -2
  10. package/lib/browser/walkthrough-section.d.ts.map +1 -1
  11. package/lib/browser/walkthrough-section.js +7 -5
  12. package/lib/browser/walkthrough-section.js.map +1 -1
  13. package/lib/browser/walkthrough-section.spec.js +17 -1
  14. package/lib/browser/walkthrough-section.spec.js.map +1 -1
  15. package/lib/browser/walkthrough-service.d.ts +26 -5
  16. package/lib/browser/walkthrough-service.d.ts.map +1 -1
  17. package/lib/browser/walkthrough-service.js +77 -22
  18. package/lib/browser/walkthrough-service.js.map +1 -1
  19. package/lib/browser/walkthrough-service.spec.js +173 -57
  20. package/lib/browser/walkthrough-service.spec.js.map +1 -1
  21. package/lib/common/walkthrough-provider.d.ts +22 -0
  22. package/lib/common/walkthrough-provider.d.ts.map +1 -0
  23. package/lib/common/walkthrough-provider.js +27 -0
  24. package/lib/common/walkthrough-provider.js.map +1 -0
  25. package/lib/common/walkthrough-types.d.ts +18 -5
  26. package/lib/common/walkthrough-types.d.ts.map +1 -1
  27. package/package.json +8 -8
  28. package/src/browser/getting-started-frontend-module.ts +3 -0
  29. package/src/browser/getting-started-widget.tsx +31 -101
  30. package/src/browser/style/index.css +69 -35
  31. package/src/browser/walkthrough-section.spec.tsx +29 -1
  32. package/src/browser/walkthrough-section.tsx +7 -5
  33. package/src/browser/walkthrough-service.spec.ts +207 -57
  34. package/src/browser/walkthrough-service.ts +82 -25
  35. package/src/common/walkthrough-provider.ts +38 -0
  36. package/src/common/walkthrough-types.ts +20 -5
@@ -21,6 +21,8 @@ import { WalkthroughService } from './walkthrough-service';
21
21
  import { Emitter } from '@theia/core/lib/common/event';
22
22
  import { WalkthroughContribution } from '@theia/plugin-ext/lib/common/plugin-protocol';
23
23
  import { ContextKeyChangeEvent } from '@theia/core/lib/browser/context-key-service';
24
+ import { WalkthroughProvider } from '../common/walkthrough-provider';
25
+ import { WalkthroughDefinition } from '../common/walkthrough-types';
24
26
 
25
27
  describe('WalkthroughService', () => {
26
28
 
@@ -40,6 +42,7 @@ describe('WalkthroughService', () => {
40
42
  let mockOpenerOpenCalls: string[];
41
43
  let reportedErrors: string[];
42
44
  let mockDisabledByTrust: Set<string>;
45
+ let mockWalkthroughProviders: WalkthroughProvider[];
43
46
 
44
47
  function createContribution(overrides?: Partial<WalkthroughContribution>): WalkthroughContribution {
45
48
  return {
@@ -65,6 +68,12 @@ describe('WalkthroughService', () => {
65
68
  };
66
69
  }
67
70
 
71
+ /** Registers a plugin contribution the way {@link WalkthroughService.collectPluginWalkthroughs} does. */
72
+ function registerContribution(contribution: WalkthroughContribution): void {
73
+ (service as any).registerWalkthrough(
74
+ `${contribution.pluginId}.${contribution.id}`, contribution, contribution.pluginId, contribution.pluginIcon);
75
+ }
76
+
68
77
  beforeEach(() => {
69
78
  storedData = {};
70
79
  onDidChangePluginsEmitter = new Emitter();
@@ -78,6 +87,7 @@ describe('WalkthroughService', () => {
78
87
  mockOpenerOpenCalls = [];
79
88
  reportedErrors = [];
80
89
  mockDisabledByTrust = new Set();
90
+ mockWalkthroughProviders = [];
81
91
  const mockOpener = { open: (...args: unknown[]) => { mockOpenerOpenCalls.push(String(args[0])); } };
82
92
  mockOpenerGetOpener = () => Promise.resolve(mockOpener);
83
93
  onDidExpandViewEmitter = new Emitter();
@@ -138,6 +148,9 @@ describe('WalkthroughService', () => {
138
148
  get disabledByTrust(): ReadonlySet<string> { return mockDisabledByTrust; },
139
149
  onDidChangePlugins: onDidChangePluginsEmitter.event
140
150
  };
151
+ (service as any).walkthroughProviders = {
152
+ getContributions: () => mockWalkthroughProviders
153
+ };
141
154
  });
142
155
 
143
156
  afterEach(() => {
@@ -152,7 +165,7 @@ describe('WalkthroughService', () => {
152
165
  describe('registerWalkthrough', () => {
153
166
  it('should register a walkthrough and make it retrievable', () => {
154
167
  const contribution = createContribution();
155
- (service as any).registerWalkthrough(contribution);
168
+ registerContribution(contribution);
156
169
 
157
170
  const walkthroughs = service.getWalkthroughs();
158
171
  expect(walkthroughs).to.have.lengthOf(1);
@@ -163,7 +176,7 @@ describe('WalkthroughService', () => {
163
176
 
164
177
  it('should construct full ID from pluginId and walkthrough id', () => {
165
178
  const contribution = createContribution({ pluginId: 'my.plugin', id: 'my-wt' });
166
- (service as any).registerWalkthrough(contribution);
179
+ registerContribution(contribution);
167
180
 
168
181
  expect(service.getWalkthrough('my.plugin.my-wt')).to.not.be.undefined;
169
182
  });
@@ -172,12 +185,12 @@ describe('WalkthroughService', () => {
172
185
  let fired = false;
173
186
  service.onDidChangeWalkthroughs(() => { fired = true; });
174
187
 
175
- (service as any).registerWalkthrough(createContribution());
188
+ registerContribution(createContribution());
176
189
  expect(fired).to.be.true;
177
190
  });
178
191
 
179
192
  it('should initialize steps as not complete', () => {
180
- (service as any).registerWalkthrough(createContribution());
193
+ registerContribution(createContribution());
181
194
  const walkthrough = service.getWalkthroughs()[0];
182
195
  for (const step of walkthrough.steps) {
183
196
  expect(step.isComplete).to.be.false;
@@ -187,7 +200,7 @@ describe('WalkthroughService', () => {
187
200
 
188
201
  describe('markStepComplete', () => {
189
202
  beforeEach(() => {
190
- (service as any).registerWalkthrough(createContribution());
203
+ registerContribution(createContribution());
191
204
  });
192
205
 
193
206
  it('should mark a step as complete', async () => {
@@ -243,7 +256,7 @@ describe('WalkthroughService', () => {
243
256
  const wtId = 'test.publisher.test-plugin.test-walkthrough';
244
257
 
245
258
  beforeEach(() => {
246
- (service as any).registerWalkthrough(createContribution());
259
+ registerContribution(createContribution());
247
260
  });
248
261
 
249
262
  it('should take the completion mark off a step again', async () => {
@@ -293,7 +306,7 @@ describe('WalkthroughService', () => {
293
306
  const wtId = 'test.publisher.test-plugin.test-walkthrough';
294
307
 
295
308
  beforeEach(() => {
296
- (service as any).registerWalkthrough(createContribution());
309
+ registerContribution(createContribution());
297
310
  });
298
311
 
299
312
  it('should complete every step', async () => {
@@ -333,7 +346,7 @@ describe('WalkthroughService', () => {
333
346
 
334
347
  describe('resetProgress', () => {
335
348
  beforeEach(async () => {
336
- (service as any).registerWalkthrough(createContribution());
349
+ registerContribution(createContribution());
337
350
  const wtId = 'test.publisher.test-plugin.test-walkthrough';
338
351
  await service.markStepComplete(wtId, 'step1');
339
352
  await service.markStepComplete(wtId, 'step2');
@@ -365,7 +378,7 @@ describe('WalkthroughService', () => {
365
378
 
366
379
  describe('getStepProgress', () => {
367
380
  beforeEach(() => {
368
- (service as any).registerWalkthrough(createContribution());
381
+ registerContribution(createContribution());
369
382
  });
370
383
 
371
384
  it('should return correct progress', () => {
@@ -393,7 +406,7 @@ describe('WalkthroughService', () => {
393
406
 
394
407
  describe('completion event handling', () => {
395
408
  beforeEach(() => {
396
- (service as any).registerWalkthrough(createContribution());
409
+ registerContribution(createContribution());
397
410
  // Wire up the event handlers that @postConstruct would normally set up
398
411
  (service as any).toDispose.push(
399
412
  (service as any).commandRegistry.onDidExecuteCommand((e: { commandId: string }) => {
@@ -452,7 +465,7 @@ describe('WalkthroughService', () => {
452
465
 
453
466
  // Simulate what loadProgress does
454
467
  await (service as any).loadProgress();
455
- (service as any).registerWalkthrough(createContribution());
468
+ registerContribution(createContribution());
456
469
 
457
470
  const wtId = 'test.publisher.test-plugin.test-walkthrough';
458
471
  const walkthrough = service.getWalkthrough(wtId)!;
@@ -490,7 +503,7 @@ describe('WalkthroughService', () => {
490
503
 
491
504
  it('should handle missing storage data gracefully', async () => {
492
505
  await (service as any).loadProgress();
493
- (service as any).registerWalkthrough(createContribution());
506
+ registerContribution(createContribution());
494
507
 
495
508
  const wtId = 'test.publisher.test-plugin.test-walkthrough';
496
509
  const walkthrough = service.getWalkthrough(wtId)!;
@@ -502,14 +515,14 @@ describe('WalkthroughService', () => {
502
515
 
503
516
  describe('extension icon', () => {
504
517
  it('should carry the icon of the contributing extension', () => {
505
- (service as any).registerWalkthrough(createContribution({ pluginIcon: 'hostedPlugin/pub_name/icon.png' }));
518
+ registerContribution(createContribution({ pluginIcon: 'hostedPlugin/pub_name/icon.png' }));
506
519
 
507
520
  const walkthrough = service.getWalkthrough('test.publisher.test-plugin.test-walkthrough')!;
508
521
  expect(walkthrough.pluginIcon).to.equal('hostedPlugin/pub_name/icon.png');
509
522
  });
510
523
 
511
524
  it('should leave the icon undefined when the extension declares none', () => {
512
- (service as any).registerWalkthrough(createContribution());
525
+ registerContribution(createContribution());
513
526
 
514
527
  const walkthrough = service.getWalkthrough('test.publisher.test-plugin.test-walkthrough')!;
515
528
  expect(walkthrough.pluginIcon).to.be.undefined;
@@ -525,7 +538,7 @@ describe('WalkthroughService', () => {
525
538
  });
526
539
 
527
540
  it('should retain the selected walkthrough and fire onDidChangeSelection', () => {
528
- (service as any).registerWalkthrough(createContribution());
541
+ registerContribution(createContribution());
529
542
 
530
543
  let fired = 0;
531
544
  service.onDidChangeSelection(() => { fired++; });
@@ -536,14 +549,14 @@ describe('WalkthroughService', () => {
536
549
  });
537
550
 
538
551
  it('should preselect the first pending step', () => {
539
- (service as any).registerWalkthrough(createContribution());
552
+ registerContribution(createContribution());
540
553
  service.selectWalkthrough(wtId);
541
554
 
542
555
  expect(service.selectedStep?.id).to.equal('step1');
543
556
  });
544
557
 
545
558
  it('should preselect the first step that is not yet complete', async () => {
546
- (service as any).registerWalkthrough(createContribution());
559
+ registerContribution(createContribution());
547
560
  await service.markStepComplete(wtId, 'step1');
548
561
  service.selectWalkthrough(wtId);
549
562
 
@@ -551,7 +564,7 @@ describe('WalkthroughService', () => {
551
564
  });
552
565
 
553
566
  it('should accept the VS Code `publisher.name#walkthroughId` form', () => {
554
- (service as any).registerWalkthrough(createContribution());
567
+ registerContribution(createContribution());
555
568
  service.selectWalkthrough('test.publisher.test-plugin#test-walkthrough');
556
569
 
557
570
  expect(service.selectedWalkthrough?.id).to.equal(wtId);
@@ -567,7 +580,7 @@ describe('WalkthroughService', () => {
567
580
  });
568
581
 
569
582
  it('should select another step of the selected walkthrough', () => {
570
- (service as any).registerWalkthrough(createContribution());
583
+ registerContribution(createContribution());
571
584
  service.selectWalkthrough(wtId);
572
585
  service.selectStep('step2');
573
586
 
@@ -575,7 +588,7 @@ describe('WalkthroughService', () => {
575
588
  });
576
589
 
577
590
  it('should ignore a step that does not belong to the selected walkthrough', () => {
578
- (service as any).registerWalkthrough(createContribution());
591
+ registerContribution(createContribution());
579
592
  service.selectWalkthrough(wtId);
580
593
  service.selectStep('nonexistent');
581
594
 
@@ -583,7 +596,7 @@ describe('WalkthroughService', () => {
583
596
  });
584
597
 
585
598
  it('should clear the selection', () => {
586
- (service as any).registerWalkthrough(createContribution());
599
+ registerContribution(createContribution());
587
600
  service.selectWalkthrough(wtId);
588
601
 
589
602
  let fired = 0;
@@ -604,7 +617,7 @@ describe('WalkthroughService', () => {
604
617
  });
605
618
 
606
619
  it('should reflect step completion in the selected step', async () => {
607
- (service as any).registerWalkthrough(createContribution());
620
+ registerContribution(createContribution());
608
621
  service.selectWalkthrough(wtId);
609
622
  await service.markStepComplete(wtId, 'step1');
610
623
 
@@ -618,7 +631,7 @@ describe('WalkthroughService', () => {
618
631
 
619
632
  it('should hide a walkthrough whose when clause does not hold', () => {
620
633
  mockContextKeyMatchResult = false;
621
- (service as any).registerWalkthrough(createContribution({ when: 'someKey' }));
634
+ registerContribution(createContribution({ when: 'someKey' }));
622
635
 
623
636
  expect(service.getWalkthroughs()).to.be.empty;
624
637
  expect(service.getWalkthrough(wtId)).to.be.undefined;
@@ -626,21 +639,21 @@ describe('WalkthroughService', () => {
626
639
 
627
640
  it('should list a walkthrough whose when clause holds', () => {
628
641
  mockContextKeyMatchResult = true;
629
- (service as any).registerWalkthrough(createContribution({ when: 'someKey' }));
642
+ registerContribution(createContribution({ when: 'someKey' }));
630
643
 
631
644
  expect(service.getWalkthroughs()).to.have.lengthOf(1);
632
645
  });
633
646
 
634
647
  it('should always list a walkthrough without a when clause', () => {
635
648
  mockContextKeyMatchResult = false;
636
- (service as any).registerWalkthrough(createContribution());
649
+ registerContribution(createContribution());
637
650
 
638
651
  expect(service.getWalkthroughs()).to.have.lengthOf(1);
639
652
  });
640
653
 
641
654
  it('should hide the steps whose when clause does not hold', () => {
642
655
  mockContextKeyMatchResult = false;
643
- (service as any).registerWalkthrough(createContribution({
656
+ registerContribution(createContribution({
644
657
  steps: [
645
658
  { id: 'always', title: 'Always', description: 'd' },
646
659
  { id: 'never', title: 'Never', description: 'd', when: 'someKey' }
@@ -654,7 +667,7 @@ describe('WalkthroughService', () => {
654
667
 
655
668
  it('should exclude hidden steps from the progress', () => {
656
669
  mockContextKeyMatchResult = false;
657
- (service as any).registerWalkthrough(createContribution({
670
+ registerContribution(createContribution({
658
671
  steps: [
659
672
  { id: 'always', title: 'Always', description: 'd' },
660
673
  { id: 'never', title: 'Never', description: 'd', when: 'someKey' }
@@ -666,7 +679,7 @@ describe('WalkthroughService', () => {
666
679
 
667
680
  it('should not select a hidden walkthrough', () => {
668
681
  mockContextKeyMatchResult = false;
669
- (service as any).registerWalkthrough(createContribution({ when: 'someKey' }));
682
+ registerContribution(createContribution({ when: 'someKey' }));
670
683
  service.selectWalkthrough(wtId);
671
684
 
672
685
  expect(service.selectedWalkthrough).to.be.undefined;
@@ -674,7 +687,7 @@ describe('WalkthroughService', () => {
674
687
 
675
688
  it('should preselect the first pending visible step', () => {
676
689
  mockContextKeyMatchResult = false;
677
- (service as any).registerWalkthrough(createContribution({
690
+ registerContribution(createContribution({
678
691
  steps: [
679
692
  { id: 'hidden', title: 'Hidden', description: 'd', when: 'someKey' },
680
693
  { id: 'visible', title: 'Visible', description: 'd' }
@@ -687,7 +700,7 @@ describe('WalkthroughService', () => {
687
700
 
688
701
  it('should re-evaluate visibility whenever it is queried', () => {
689
702
  mockContextKeyMatchResult = false;
690
- (service as any).registerWalkthrough(createContribution({ when: 'someKey' }));
703
+ registerContribution(createContribution({ when: 'someKey' }));
691
704
  expect(service.getWalkthroughs()).to.be.empty;
692
705
 
693
706
  mockContextKeyMatchResult = true;
@@ -695,7 +708,7 @@ describe('WalkthroughService', () => {
695
708
  });
696
709
 
697
710
  it('should collect the context keys of every when clause and onContext event', () => {
698
- (service as any).registerWalkthrough(createContribution({
711
+ registerContribution(createContribution({
699
712
  when: 'walkthroughKey',
700
713
  steps: [{ id: 's1', title: 'S1', description: 'd', when: 'stepKey', completionEvents: ['onContext:eventKey'] }]
701
714
  }));
@@ -705,7 +718,7 @@ describe('WalkthroughService', () => {
705
718
  });
706
719
 
707
720
  it('should notify when visibility changed', () => {
708
- (service as any).registerWalkthrough(createContribution({ when: 'someKey' }));
721
+ registerContribution(createContribution({ when: 'someKey' }));
709
722
 
710
723
  let fired = false;
711
724
  service.onDidChangeWalkthroughs(() => { fired = true; });
@@ -716,7 +729,7 @@ describe('WalkthroughService', () => {
716
729
 
717
730
  it('should close a selected walkthrough that becomes hidden', () => {
718
731
  mockContextKeyMatchResult = true;
719
- (service as any).registerWalkthrough(createContribution({ when: 'someKey' }));
732
+ registerContribution(createContribution({ when: 'someKey' }));
720
733
  service.selectWalkthrough(wtId);
721
734
  expect(service.selectedWalkthrough?.id).to.equal(wtId);
722
735
 
@@ -728,7 +741,7 @@ describe('WalkthroughService', () => {
728
741
 
729
742
  it('should mark only the visible steps done', async () => {
730
743
  mockContextKeyMatchResult = false;
731
- (service as any).registerWalkthrough(createContribution({
744
+ registerContribution(createContribution({
732
745
  steps: [
733
746
  { id: 'always', title: 'Always', description: 'd' },
734
747
  { id: 'never', title: 'Never', description: 'd', when: 'someKey' }
@@ -751,8 +764,8 @@ describe('WalkthroughService', () => {
751
764
  });
752
765
 
753
766
  it('should return all registered walkthroughs', () => {
754
- (service as any).registerWalkthrough(createContribution({ id: 'wt1' }));
755
- (service as any).registerWalkthrough(createContribution({ id: 'wt2' }));
767
+ registerContribution(createContribution({ id: 'wt1' }));
768
+ registerContribution(createContribution({ id: 'wt2' }));
756
769
 
757
770
  const walkthroughs = service.getWalkthroughs();
758
771
  expect(walkthroughs).to.have.lengthOf(2);
@@ -766,7 +779,7 @@ describe('WalkthroughService', () => {
766
779
  beforeEach(() => {
767
780
  mockPlugins = [{ model: { id: pluginId, publisher: 'test.publisher', name: 'test-plugin' } }];
768
781
  mockDeployedPlugins.set(pluginId, { contributes: { walkthroughs: [createContribution()] } });
769
- (service as any).syncWalkthroughsFromPlugins();
782
+ (service as any).syncWalkthroughs();
770
783
  });
771
784
 
772
785
  it('should offer the walkthrough of an installed plugin', () => {
@@ -784,7 +797,7 @@ describe('WalkthroughService', () => {
784
797
  })]
785
798
  }
786
799
  });
787
- (service as any).syncWalkthroughsFromPlugins();
800
+ (service as any).syncWalkthroughs();
788
801
 
789
802
  const steps = service.getWalkthrough(wtId)!.steps;
790
803
  expect(steps).to.have.lengthOf(1);
@@ -794,7 +807,7 @@ describe('WalkthroughService', () => {
794
807
  it('should keep a walkthrough whose definition is unchanged', () => {
795
808
  let fired = false;
796
809
  service.onDidChangeWalkthroughs(() => { fired = true; });
797
- (service as any).syncWalkthroughsFromPlugins();
810
+ (service as any).syncWalkthroughs();
798
811
 
799
812
  expect(fired, 'an unchanged sync must not notify').to.be.false;
800
813
  });
@@ -803,18 +816,18 @@ describe('WalkthroughService', () => {
803
816
  expect(service.getWalkthrough(wtId), 'offered while the workspace is trusted').to.not.be.undefined;
804
817
 
805
818
  mockDisabledByTrust.add(pluginId);
806
- (service as any).syncWalkthroughsFromPlugins();
819
+ (service as any).syncWalkthroughs();
807
820
 
808
821
  expect(service.getWalkthroughs()).to.be.empty;
809
822
  });
810
823
 
811
824
  it('should offer it again once the workspace is trusted', () => {
812
825
  mockDisabledByTrust.add(pluginId);
813
- (service as any).syncWalkthroughsFromPlugins();
826
+ (service as any).syncWalkthroughs();
814
827
  expect(service.getWalkthroughs()).to.be.empty;
815
828
 
816
829
  mockDisabledByTrust.delete(pluginId);
817
- (service as any).syncWalkthroughsFromPlugins();
830
+ (service as any).syncWalkthroughs();
818
831
 
819
832
  expect(service.getWalkthrough(wtId)).to.not.be.undefined;
820
833
  });
@@ -824,7 +837,7 @@ describe('WalkthroughService', () => {
824
837
  expect(service.selectedWalkthrough?.id).to.equal(wtId);
825
838
 
826
839
  mockDisabledByTrust.add(pluginId);
827
- (service as any).syncWalkthroughsFromPlugins();
840
+ (service as any).syncWalkthroughs();
828
841
 
829
842
  expect(service.selectedWalkthrough).to.be.undefined;
830
843
  });
@@ -832,7 +845,7 @@ describe('WalkthroughService', () => {
832
845
  it('should drop the walkthrough once the plugin is out of sync', () => {
833
846
  // Theia keeps an uninstalled or disabled plugin loaded until the next reload, flagged as out of sync.
834
847
  mockPlugins[0].outOfSync = true;
835
- (service as any).syncWalkthroughsFromPlugins();
848
+ (service as any).syncWalkthroughs();
836
849
 
837
850
  expect(service.getWalkthrough(wtId)).to.be.undefined;
838
851
  expect(service.getWalkthroughs()).to.be.empty;
@@ -842,7 +855,7 @@ describe('WalkthroughService', () => {
842
855
  let fired = false;
843
856
  service.onDidChangeWalkthroughs(() => { fired = true; });
844
857
  mockPlugins[0].outOfSync = true;
845
- (service as any).syncWalkthroughsFromPlugins();
858
+ (service as any).syncWalkthroughs();
846
859
 
847
860
  expect(fired).to.be.true;
848
861
  });
@@ -852,12 +865,149 @@ describe('WalkthroughService', () => {
852
865
  expect(service.selectedWalkthrough?.id).to.equal(wtId);
853
866
 
854
867
  mockPlugins[0].outOfSync = true;
855
- (service as any).syncWalkthroughsFromPlugins();
868
+ (service as any).syncWalkthroughs();
856
869
 
857
870
  expect(service.selectedWalkthrough).to.be.undefined;
858
871
  });
859
872
  });
860
873
 
874
+ describe('contributed walkthroughs', () => {
875
+ const providedId = 'theia.test-walkthrough';
876
+ const pluginId = 'test.publisher.test-plugin';
877
+ const pluginWalkthroughId = 'test.publisher.test-plugin.test-walkthrough';
878
+
879
+ function createDefinition(overrides?: Partial<WalkthroughDefinition>): WalkthroughDefinition {
880
+ return {
881
+ id: providedId,
882
+ title: 'Provided Walkthrough',
883
+ description: 'Contributed by a Theia extension',
884
+ icon: 'sparkle',
885
+ steps: [
886
+ { id: 'first', title: 'First', description: 'First step' },
887
+ { id: 'second', title: 'Second', description: 'Second step' }
888
+ ],
889
+ ...overrides
890
+ };
891
+ }
892
+
893
+ function provide(...definitions: WalkthroughDefinition[]): void {
894
+ mockWalkthroughProviders = [{ getWalkthroughs: () => definitions }];
895
+ }
896
+
897
+ it('should register the walkthroughs of a provider under their own id', () => {
898
+ provide(createDefinition());
899
+ (service as any).syncWalkthroughs();
900
+
901
+ const walkthrough = service.getWalkthrough(providedId);
902
+ expect(walkthrough).to.not.be.undefined;
903
+ expect(walkthrough!.title).to.equal('Provided Walkthrough');
904
+ expect(walkthrough!.icon).to.equal('sparkle');
905
+ expect(walkthrough!.pluginId).to.be.undefined;
906
+ expect(walkthrough!.steps.map(step => step.isComplete)).to.deep.equal([false, false]);
907
+ });
908
+
909
+ it('should keep plugin and provided walkthroughs alongside each other', () => {
910
+ mockPlugins = [{ model: { id: pluginId, publisher: 'test.publisher', name: 'test-plugin' } }];
911
+ mockDeployedPlugins.set(pluginId, { contributes: { walkthroughs: [createContribution()] } });
912
+ provide(createDefinition());
913
+ (service as any).syncWalkthroughs();
914
+
915
+ expect(service.getWalkthroughs().map(walkthrough => walkthrough.id)).to.have.members([pluginWalkthroughId, providedId]);
916
+ });
917
+
918
+ it('should not drop a provided walkthrough when plugins change', async () => {
919
+ provide(createDefinition());
920
+ (service as any).syncWalkthroughs();
921
+
922
+ mockPlugins = [{ model: { id: pluginId, publisher: 'test.publisher', name: 'test-plugin' } }];
923
+ mockDeployedPlugins.set(pluginId, { contributes: { walkthroughs: [createContribution()] } });
924
+ await (service as any).handlePluginsChanged();
925
+
926
+ expect(service.getWalkthrough(providedId)).to.not.be.undefined;
927
+ });
928
+
929
+ it('should keep the progress of a re-synced, unchanged walkthrough', async () => {
930
+ provide(createDefinition());
931
+ (service as any).syncWalkthroughs();
932
+ await service.markStepComplete(providedId, 'first');
933
+
934
+ (service as any).syncWalkthroughs();
935
+
936
+ expect(service.getWalkthrough(providedId)!.steps[0].isComplete).to.be.true;
937
+ });
938
+
939
+ it('should re-register a walkthrough whose definition changed', () => {
940
+ provide(createDefinition());
941
+ (service as any).syncWalkthroughs();
942
+ expect(service.getWalkthrough(providedId)!.steps).to.have.lengthOf(2);
943
+
944
+ provide(createDefinition({ steps: [{ id: 'only', title: 'Only', description: 'Only step' }] }));
945
+ (service as any).syncWalkthroughs();
946
+
947
+ expect(service.getWalkthrough(providedId)!.steps).to.have.lengthOf(1);
948
+ });
949
+
950
+ it('should drop a walkthrough that is no longer provided', () => {
951
+ provide(createDefinition());
952
+ (service as any).syncWalkthroughs();
953
+
954
+ provide();
955
+ (service as any).syncWalkthroughs();
956
+
957
+ expect(service.getWalkthrough(providedId)).to.be.undefined;
958
+ });
959
+
960
+ it('should keep the other walkthroughs when a provider throws', () => {
961
+ mockWalkthroughProviders = [
962
+ { getWalkthroughs: () => { throw new Error('provider is broken'); } },
963
+ { getWalkthroughs: () => [createDefinition()] }
964
+ ];
965
+
966
+ (service as any).syncWalkthroughs();
967
+
968
+ expect(service.getWalkthrough(providedId)).to.not.be.undefined;
969
+ });
970
+
971
+ it('should not reject the sync when a provider throws', async () => {
972
+ mockWalkthroughProviders = [{ getWalkthroughs: () => { throw new Error('provider is broken'); } }];
973
+ (service as any).init();
974
+
975
+ // A rejected `progressReady` would stop every later sync, so it has to survive a failing provider.
976
+ await (service as any).progressReady;
977
+ });
978
+
979
+ it('should ignore a walkthrough whose id is already taken', () => {
980
+ mockWalkthroughProviders = [
981
+ { getWalkthroughs: () => [createDefinition({ title: 'First' })] },
982
+ { getWalkthroughs: () => [createDefinition({ title: 'Second' })] }
983
+ ];
984
+
985
+ (service as any).syncWalkthroughs();
986
+
987
+ expect(service.getWalkthroughs()).to.have.lengthOf(1);
988
+ expect(service.getWalkthrough(providedId)!.title).to.equal('First');
989
+ });
990
+
991
+ it('should re-sync when a provider signals a change', async () => {
992
+ const onDidChangeEmitter = new Emitter<void>();
993
+ let definitions: WalkthroughDefinition[] = [];
994
+ mockWalkthroughProviders = [{
995
+ getWalkthroughs: () => definitions,
996
+ onDidChange: onDidChangeEmitter.event
997
+ }];
998
+ (service as any).init();
999
+ await (service as any).progressReady;
1000
+ expect(service.getWalkthroughs()).to.be.empty;
1001
+
1002
+ definitions = [createDefinition()];
1003
+ onDidChangeEmitter.fire();
1004
+ await (service as any).progressReady;
1005
+
1006
+ expect(service.getWalkthrough(providedId)).to.not.be.undefined;
1007
+ onDidChangeEmitter.dispose();
1008
+ });
1009
+ });
1010
+
861
1011
  describe('extensionInstalled completion event', () => {
862
1012
  const walkthroughContribution = createContribution({
863
1013
  steps: [
@@ -879,14 +1029,14 @@ describe('WalkthroughService', () => {
879
1029
  }
880
1030
 
881
1031
  beforeEach(() => {
882
- // Set up the plugin so the walkthrough is discovered via syncWalkthroughsFromPlugins
1032
+ // Set up the plugin so the walkthrough is discovered via syncWalkthroughs
883
1033
  setupPluginWithWalkthrough();
884
- (service as any).syncWalkthroughsFromPlugins();
1034
+ (service as any).syncWalkthroughs();
885
1035
  // Wire up the onDidChangePlugins handler manually (simulating @postConstruct)
886
1036
  (service as any).toDispose.push(
887
1037
  (service as any).pluginSupport.onDidChangePlugins(() => {
888
1038
  const previousIds = new Set((service as any).knownPluginIds);
889
- (service as any).syncWalkthroughsFromPlugins();
1039
+ (service as any).syncWalkthroughs();
890
1040
  const newIds = new Set(
891
1041
  (service as any).pluginSupport.plugins.map((p: any) => p.model.id)
892
1042
  );
@@ -955,7 +1105,7 @@ describe('WalkthroughService', () => {
955
1105
 
956
1106
  describe('onContext completion event', () => {
957
1107
  beforeEach(() => {
958
- (service as any).registerWalkthrough(createContribution({
1108
+ registerContribution(createContribution({
959
1109
  steps: [
960
1110
  {
961
1111
  id: 'context-step',
@@ -1030,7 +1180,7 @@ describe('WalkthroughService', () => {
1030
1180
  describe('onContext expressions', () => {
1031
1181
  it('should complete a step whose context expression already holds at registration', () => {
1032
1182
  mockContextKeyMatchResult = true;
1033
- (service as any).registerWalkthrough(createContribution({
1183
+ registerContribution(createContribution({
1034
1184
  steps: [{ id: 'static', title: 'Static', description: 'd', completionEvents: ['onContext:isLinux'] }]
1035
1185
  }));
1036
1186
 
@@ -1039,7 +1189,7 @@ describe('WalkthroughService', () => {
1039
1189
  });
1040
1190
 
1041
1191
  it('should react to a context expression rather than treating it as a single key', () => {
1042
- (service as any).registerWalkthrough(createContribution({
1192
+ registerContribution(createContribution({
1043
1193
  steps: [{ id: 'expr', title: 'Expression', description: 'd', completionEvents: ['onContext:foo && bar'] }]
1044
1194
  }));
1045
1195
 
@@ -1057,7 +1207,7 @@ describe('WalkthroughService', () => {
1057
1207
 
1058
1208
  describe('onView completion event', () => {
1059
1209
  beforeEach(() => {
1060
- (service as any).registerWalkthrough(createContribution({
1210
+ registerContribution(createContribution({
1061
1211
  steps: [
1062
1212
  {
1063
1213
  id: 'view-step',
@@ -1097,7 +1247,7 @@ describe('WalkthroughService', () => {
1097
1247
 
1098
1248
  it('should handle multiple onView completion events on different steps', async () => {
1099
1249
  // Register a second walkthrough with a different view event
1100
- (service as any).registerWalkthrough(createContribution({
1250
+ registerContribution(createContribution({
1101
1251
  id: 'multi-view-walkthrough',
1102
1252
  steps: [
1103
1253
  {
@@ -1134,7 +1284,7 @@ describe('WalkthroughService', () => {
1134
1284
 
1135
1285
  describe('onLink completion event', () => {
1136
1286
  beforeEach(() => {
1137
- (service as any).registerWalkthrough(createContribution({
1287
+ registerContribution(createContribution({
1138
1288
  steps: [
1139
1289
  {
1140
1290
  id: 'link-step',
@@ -1169,7 +1319,7 @@ describe('WalkthroughService', () => {
1169
1319
  });
1170
1320
 
1171
1321
  it('should complete step with command: URI', async () => {
1172
- (service as any).registerWalkthrough(createContribution({
1322
+ registerContribution(createContribution({
1173
1323
  id: 'cmd-link-walkthrough',
1174
1324
  steps: [
1175
1325
  {
@@ -1234,7 +1384,7 @@ describe('WalkthroughService', () => {
1234
1384
  (service as any).toDispose.push(
1235
1385
  (service as any).pluginSupport.onDidChangePlugins(() => {
1236
1386
  const previousIds = new Set((service as any).knownPluginIds);
1237
- (service as any).syncWalkthroughsFromPlugins();
1387
+ (service as any).syncWalkthroughs();
1238
1388
  const newIds = new Set(
1239
1389
  (service as any).pluginSupport.plugins.map((p: any) => p.model.id)
1240
1390
  );