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

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
@@ -36,6 +36,7 @@ describe('WalkthroughService', () => {
36
36
  let mockOpenerOpenCalls;
37
37
  let reportedErrors;
38
38
  let mockDisabledByTrust;
39
+ let mockWalkthroughProviders;
39
40
  function createContribution(overrides) {
40
41
  return {
41
42
  id: 'test-walkthrough',
@@ -59,6 +60,10 @@ describe('WalkthroughService', () => {
59
60
  ...overrides
60
61
  };
61
62
  }
63
+ /** Registers a plugin contribution the way {@link WalkthroughService.collectPluginWalkthroughs} does. */
64
+ function registerContribution(contribution) {
65
+ service.registerWalkthrough(`${contribution.pluginId}.${contribution.id}`, contribution, contribution.pluginId, contribution.pluginIcon);
66
+ }
62
67
  beforeEach(() => {
63
68
  storedData = {};
64
69
  onDidChangePluginsEmitter = new event_1.Emitter();
@@ -72,6 +77,7 @@ describe('WalkthroughService', () => {
72
77
  mockOpenerOpenCalls = [];
73
78
  reportedErrors = [];
74
79
  mockDisabledByTrust = new Set();
80
+ mockWalkthroughProviders = [];
75
81
  const mockOpener = { open: (...args) => { mockOpenerOpenCalls.push(String(args[0])); } };
76
82
  mockOpenerGetOpener = () => Promise.resolve(mockOpener);
77
83
  onDidExpandViewEmitter = new event_1.Emitter();
@@ -130,6 +136,9 @@ describe('WalkthroughService', () => {
130
136
  get disabledByTrust() { return mockDisabledByTrust; },
131
137
  onDidChangePlugins: onDidChangePluginsEmitter.event
132
138
  };
139
+ service.walkthroughProviders = {
140
+ getContributions: () => mockWalkthroughProviders
141
+ };
133
142
  });
134
143
  afterEach(() => {
135
144
  service.dispose();
@@ -142,7 +151,7 @@ describe('WalkthroughService', () => {
142
151
  describe('registerWalkthrough', () => {
143
152
  it('should register a walkthrough and make it retrievable', () => {
144
153
  const contribution = createContribution();
145
- service.registerWalkthrough(contribution);
154
+ registerContribution(contribution);
146
155
  const walkthroughs = service.getWalkthroughs();
147
156
  (0, chai_1.expect)(walkthroughs).to.have.lengthOf(1);
148
157
  (0, chai_1.expect)(walkthroughs[0].id).to.equal('test.publisher.test-plugin.test-walkthrough');
@@ -151,17 +160,17 @@ describe('WalkthroughService', () => {
151
160
  });
152
161
  it('should construct full ID from pluginId and walkthrough id', () => {
153
162
  const contribution = createContribution({ pluginId: 'my.plugin', id: 'my-wt' });
154
- service.registerWalkthrough(contribution);
163
+ registerContribution(contribution);
155
164
  (0, chai_1.expect)(service.getWalkthrough('my.plugin.my-wt')).to.not.be.undefined;
156
165
  });
157
166
  it('should fire onDidChangeWalkthroughs event', () => {
158
167
  let fired = false;
159
168
  service.onDidChangeWalkthroughs(() => { fired = true; });
160
- service.registerWalkthrough(createContribution());
169
+ registerContribution(createContribution());
161
170
  (0, chai_1.expect)(fired).to.be.true;
162
171
  });
163
172
  it('should initialize steps as not complete', () => {
164
- service.registerWalkthrough(createContribution());
173
+ registerContribution(createContribution());
165
174
  const walkthrough = service.getWalkthroughs()[0];
166
175
  for (const step of walkthrough.steps) {
167
176
  (0, chai_1.expect)(step.isComplete).to.be.false;
@@ -170,7 +179,7 @@ describe('WalkthroughService', () => {
170
179
  });
171
180
  describe('markStepComplete', () => {
172
181
  beforeEach(() => {
173
- service.registerWalkthrough(createContribution());
182
+ registerContribution(createContribution());
174
183
  });
175
184
  it('should mark a step as complete', async () => {
176
185
  const wtId = 'test.publisher.test-plugin.test-walkthrough';
@@ -214,7 +223,7 @@ describe('WalkthroughService', () => {
214
223
  describe('markStepIncomplete', () => {
215
224
  const wtId = 'test.publisher.test-plugin.test-walkthrough';
216
225
  beforeEach(() => {
217
- service.registerWalkthrough(createContribution());
226
+ registerContribution(createContribution());
218
227
  });
219
228
  it('should take the completion mark off a step again', async () => {
220
229
  await service.markStepComplete(wtId, 'step1');
@@ -250,7 +259,7 @@ describe('WalkthroughService', () => {
250
259
  describe('markAllStepsComplete', () => {
251
260
  const wtId = 'test.publisher.test-plugin.test-walkthrough';
252
261
  beforeEach(() => {
253
- service.registerWalkthrough(createContribution());
262
+ registerContribution(createContribution());
254
263
  });
255
264
  it('should complete every step', async () => {
256
265
  await service.markAllStepsComplete(wtId);
@@ -280,7 +289,7 @@ describe('WalkthroughService', () => {
280
289
  });
281
290
  describe('resetProgress', () => {
282
291
  beforeEach(async () => {
283
- service.registerWalkthrough(createContribution());
292
+ registerContribution(createContribution());
284
293
  const wtId = 'test.publisher.test-plugin.test-walkthrough';
285
294
  await service.markStepComplete(wtId, 'step1');
286
295
  await service.markStepComplete(wtId, 'step2');
@@ -306,7 +315,7 @@ describe('WalkthroughService', () => {
306
315
  });
307
316
  describe('getStepProgress', () => {
308
317
  beforeEach(() => {
309
- service.registerWalkthrough(createContribution());
318
+ registerContribution(createContribution());
310
319
  });
311
320
  it('should return correct progress', () => {
312
321
  const wtId = 'test.publisher.test-plugin.test-walkthrough';
@@ -329,7 +338,7 @@ describe('WalkthroughService', () => {
329
338
  });
330
339
  describe('completion event handling', () => {
331
340
  beforeEach(() => {
332
- service.registerWalkthrough(createContribution());
341
+ registerContribution(createContribution());
333
342
  // Wire up the event handlers that @postConstruct would normally set up
334
343
  service.toDispose.push(service.commandRegistry.onDidExecuteCommand((e) => {
335
344
  service.handleCompletionEvent(`onCommand:${e.commandId}`);
@@ -373,7 +382,7 @@ describe('WalkthroughService', () => {
373
382
  };
374
383
  // Simulate what loadProgress does
375
384
  await service.loadProgress();
376
- service.registerWalkthrough(createContribution());
385
+ registerContribution(createContribution());
377
386
  const wtId = 'test.publisher.test-plugin.test-walkthrough';
378
387
  const walkthrough = service.getWalkthrough(wtId);
379
388
  (0, chai_1.expect)(walkthrough.steps[0].isComplete).to.be.true;
@@ -404,7 +413,7 @@ describe('WalkthroughService', () => {
404
413
  });
405
414
  it('should handle missing storage data gracefully', async () => {
406
415
  await service.loadProgress();
407
- service.registerWalkthrough(createContribution());
416
+ registerContribution(createContribution());
408
417
  const wtId = 'test.publisher.test-plugin.test-walkthrough';
409
418
  const walkthrough = service.getWalkthrough(wtId);
410
419
  for (const step of walkthrough.steps) {
@@ -414,12 +423,12 @@ describe('WalkthroughService', () => {
414
423
  });
415
424
  describe('extension icon', () => {
416
425
  it('should carry the icon of the contributing extension', () => {
417
- service.registerWalkthrough(createContribution({ pluginIcon: 'hostedPlugin/pub_name/icon.png' }));
426
+ registerContribution(createContribution({ pluginIcon: 'hostedPlugin/pub_name/icon.png' }));
418
427
  const walkthrough = service.getWalkthrough('test.publisher.test-plugin.test-walkthrough');
419
428
  (0, chai_1.expect)(walkthrough.pluginIcon).to.equal('hostedPlugin/pub_name/icon.png');
420
429
  });
421
430
  it('should leave the icon undefined when the extension declares none', () => {
422
- service.registerWalkthrough(createContribution());
431
+ registerContribution(createContribution());
423
432
  const walkthrough = service.getWalkthrough('test.publisher.test-plugin.test-walkthrough');
424
433
  (0, chai_1.expect)(walkthrough.pluginIcon).to.be.undefined;
425
434
  });
@@ -431,7 +440,7 @@ describe('WalkthroughService', () => {
431
440
  (0, chai_1.expect)(service.selectedStep).to.be.undefined;
432
441
  });
433
442
  it('should retain the selected walkthrough and fire onDidChangeSelection', () => {
434
- service.registerWalkthrough(createContribution());
443
+ registerContribution(createContribution());
435
444
  let fired = 0;
436
445
  service.onDidChangeSelection(() => { fired++; });
437
446
  service.selectWalkthrough(wtId);
@@ -439,18 +448,18 @@ describe('WalkthroughService', () => {
439
448
  (0, chai_1.expect)(service.selectedWalkthrough?.id).to.equal(wtId);
440
449
  });
441
450
  it('should preselect the first pending step', () => {
442
- service.registerWalkthrough(createContribution());
451
+ registerContribution(createContribution());
443
452
  service.selectWalkthrough(wtId);
444
453
  (0, chai_1.expect)(service.selectedStep?.id).to.equal('step1');
445
454
  });
446
455
  it('should preselect the first step that is not yet complete', async () => {
447
- service.registerWalkthrough(createContribution());
456
+ registerContribution(createContribution());
448
457
  await service.markStepComplete(wtId, 'step1');
449
458
  service.selectWalkthrough(wtId);
450
459
  (0, chai_1.expect)(service.selectedStep?.id).to.equal('step2');
451
460
  });
452
461
  it('should accept the VS Code `publisher.name#walkthroughId` form', () => {
453
- service.registerWalkthrough(createContribution());
462
+ registerContribution(createContribution());
454
463
  service.selectWalkthrough('test.publisher.test-plugin#test-walkthrough');
455
464
  (0, chai_1.expect)(service.selectedWalkthrough?.id).to.equal(wtId);
456
465
  });
@@ -462,19 +471,19 @@ describe('WalkthroughService', () => {
462
471
  (0, chai_1.expect)(service.selectedWalkthrough).to.be.undefined;
463
472
  });
464
473
  it('should select another step of the selected walkthrough', () => {
465
- service.registerWalkthrough(createContribution());
474
+ registerContribution(createContribution());
466
475
  service.selectWalkthrough(wtId);
467
476
  service.selectStep('step2');
468
477
  (0, chai_1.expect)(service.selectedStep?.id).to.equal('step2');
469
478
  });
470
479
  it('should ignore a step that does not belong to the selected walkthrough', () => {
471
- service.registerWalkthrough(createContribution());
480
+ registerContribution(createContribution());
472
481
  service.selectWalkthrough(wtId);
473
482
  service.selectStep('nonexistent');
474
483
  (0, chai_1.expect)(service.selectedStep?.id).to.equal('step1');
475
484
  });
476
485
  it('should clear the selection', () => {
477
- service.registerWalkthrough(createContribution());
486
+ registerContribution(createContribution());
478
487
  service.selectWalkthrough(wtId);
479
488
  let fired = 0;
480
489
  service.onDidChangeSelection(() => { fired++; });
@@ -490,7 +499,7 @@ describe('WalkthroughService', () => {
490
499
  (0, chai_1.expect)(fired).to.be.false;
491
500
  });
492
501
  it('should reflect step completion in the selected step', async () => {
493
- service.registerWalkthrough(createContribution());
502
+ registerContribution(createContribution());
494
503
  service.selectWalkthrough(wtId);
495
504
  await service.markStepComplete(wtId, 'step1');
496
505
  (0, chai_1.expect)(service.selectedStep?.id).to.equal('step1');
@@ -501,23 +510,23 @@ describe('WalkthroughService', () => {
501
510
  const wtId = 'test.publisher.test-plugin.test-walkthrough';
502
511
  it('should hide a walkthrough whose when clause does not hold', () => {
503
512
  mockContextKeyMatchResult = false;
504
- service.registerWalkthrough(createContribution({ when: 'someKey' }));
513
+ registerContribution(createContribution({ when: 'someKey' }));
505
514
  (0, chai_1.expect)(service.getWalkthroughs()).to.be.empty;
506
515
  (0, chai_1.expect)(service.getWalkthrough(wtId)).to.be.undefined;
507
516
  });
508
517
  it('should list a walkthrough whose when clause holds', () => {
509
518
  mockContextKeyMatchResult = true;
510
- service.registerWalkthrough(createContribution({ when: 'someKey' }));
519
+ registerContribution(createContribution({ when: 'someKey' }));
511
520
  (0, chai_1.expect)(service.getWalkthroughs()).to.have.lengthOf(1);
512
521
  });
513
522
  it('should always list a walkthrough without a when clause', () => {
514
523
  mockContextKeyMatchResult = false;
515
- service.registerWalkthrough(createContribution());
524
+ registerContribution(createContribution());
516
525
  (0, chai_1.expect)(service.getWalkthroughs()).to.have.lengthOf(1);
517
526
  });
518
527
  it('should hide the steps whose when clause does not hold', () => {
519
528
  mockContextKeyMatchResult = false;
520
- service.registerWalkthrough(createContribution({
529
+ registerContribution(createContribution({
521
530
  steps: [
522
531
  { id: 'always', title: 'Always', description: 'd' },
523
532
  { id: 'never', title: 'Never', description: 'd', when: 'someKey' }
@@ -529,7 +538,7 @@ describe('WalkthroughService', () => {
529
538
  });
530
539
  it('should exclude hidden steps from the progress', () => {
531
540
  mockContextKeyMatchResult = false;
532
- service.registerWalkthrough(createContribution({
541
+ registerContribution(createContribution({
533
542
  steps: [
534
543
  { id: 'always', title: 'Always', description: 'd' },
535
544
  { id: 'never', title: 'Never', description: 'd', when: 'someKey' }
@@ -539,13 +548,13 @@ describe('WalkthroughService', () => {
539
548
  });
540
549
  it('should not select a hidden walkthrough', () => {
541
550
  mockContextKeyMatchResult = false;
542
- service.registerWalkthrough(createContribution({ when: 'someKey' }));
551
+ registerContribution(createContribution({ when: 'someKey' }));
543
552
  service.selectWalkthrough(wtId);
544
553
  (0, chai_1.expect)(service.selectedWalkthrough).to.be.undefined;
545
554
  });
546
555
  it('should preselect the first pending visible step', () => {
547
556
  mockContextKeyMatchResult = false;
548
- service.registerWalkthrough(createContribution({
557
+ registerContribution(createContribution({
549
558
  steps: [
550
559
  { id: 'hidden', title: 'Hidden', description: 'd', when: 'someKey' },
551
560
  { id: 'visible', title: 'Visible', description: 'd' }
@@ -556,13 +565,13 @@ describe('WalkthroughService', () => {
556
565
  });
557
566
  it('should re-evaluate visibility whenever it is queried', () => {
558
567
  mockContextKeyMatchResult = false;
559
- service.registerWalkthrough(createContribution({ when: 'someKey' }));
568
+ registerContribution(createContribution({ when: 'someKey' }));
560
569
  (0, chai_1.expect)(service.getWalkthroughs()).to.be.empty;
561
570
  mockContextKeyMatchResult = true;
562
571
  (0, chai_1.expect)(service.getWalkthroughs()).to.have.lengthOf(1);
563
572
  });
564
573
  it('should collect the context keys of every when clause and onContext event', () => {
565
- service.registerWalkthrough(createContribution({
574
+ registerContribution(createContribution({
566
575
  when: 'walkthroughKey',
567
576
  steps: [{ id: 's1', title: 'S1', description: 'd', when: 'stepKey', completionEvents: ['onContext:eventKey'] }]
568
577
  }));
@@ -570,7 +579,7 @@ describe('WalkthroughService', () => {
570
579
  (0, chai_1.expect)(Array.from(keys).sort()).to.deep.equal(['eventKey', 'stepKey', 'walkthroughKey']);
571
580
  });
572
581
  it('should notify when visibility changed', () => {
573
- service.registerWalkthrough(createContribution({ when: 'someKey' }));
582
+ registerContribution(createContribution({ when: 'someKey' }));
574
583
  let fired = false;
575
584
  service.onDidChangeWalkthroughs(() => { fired = true; });
576
585
  service.handleVisibilityChanged();
@@ -578,7 +587,7 @@ describe('WalkthroughService', () => {
578
587
  });
579
588
  it('should close a selected walkthrough that becomes hidden', () => {
580
589
  mockContextKeyMatchResult = true;
581
- service.registerWalkthrough(createContribution({ when: 'someKey' }));
590
+ registerContribution(createContribution({ when: 'someKey' }));
582
591
  service.selectWalkthrough(wtId);
583
592
  (0, chai_1.expect)(service.selectedWalkthrough?.id).to.equal(wtId);
584
593
  mockContextKeyMatchResult = false;
@@ -587,7 +596,7 @@ describe('WalkthroughService', () => {
587
596
  });
588
597
  it('should mark only the visible steps done', async () => {
589
598
  mockContextKeyMatchResult = false;
590
- service.registerWalkthrough(createContribution({
599
+ registerContribution(createContribution({
591
600
  steps: [
592
601
  { id: 'always', title: 'Always', description: 'd' },
593
602
  { id: 'never', title: 'Never', description: 'd', when: 'someKey' }
@@ -605,8 +614,8 @@ describe('WalkthroughService', () => {
605
614
  (0, chai_1.expect)(service.getWalkthroughs()).to.be.empty;
606
615
  });
607
616
  it('should return all registered walkthroughs', () => {
608
- service.registerWalkthrough(createContribution({ id: 'wt1' }));
609
- service.registerWalkthrough(createContribution({ id: 'wt2' }));
617
+ registerContribution(createContribution({ id: 'wt1' }));
618
+ registerContribution(createContribution({ id: 'wt2' }));
610
619
  const walkthroughs = service.getWalkthroughs();
611
620
  (0, chai_1.expect)(walkthroughs).to.have.lengthOf(2);
612
621
  });
@@ -617,7 +626,7 @@ describe('WalkthroughService', () => {
617
626
  beforeEach(() => {
618
627
  mockPlugins = [{ model: { id: pluginId, publisher: 'test.publisher', name: 'test-plugin' } }];
619
628
  mockDeployedPlugins.set(pluginId, { contributes: { walkthroughs: [createContribution()] } });
620
- service.syncWalkthroughsFromPlugins();
629
+ service.syncWalkthroughs();
621
630
  });
622
631
  it('should offer the walkthrough of an installed plugin', () => {
623
632
  (0, chai_1.expect)(service.getWalkthrough(wtId)).to.not.be.undefined;
@@ -632,7 +641,7 @@ describe('WalkthroughService', () => {
632
641
  })]
633
642
  }
634
643
  });
635
- service.syncWalkthroughsFromPlugins();
644
+ service.syncWalkthroughs();
636
645
  const steps = service.getWalkthrough(wtId).steps;
637
646
  (0, chai_1.expect)(steps).to.have.lengthOf(1);
638
647
  (0, chai_1.expect)(steps[0].id).to.equal('only');
@@ -640,34 +649,34 @@ describe('WalkthroughService', () => {
640
649
  it('should keep a walkthrough whose definition is unchanged', () => {
641
650
  let fired = false;
642
651
  service.onDidChangeWalkthroughs(() => { fired = true; });
643
- service.syncWalkthroughsFromPlugins();
652
+ service.syncWalkthroughs();
644
653
  (0, chai_1.expect)(fired, 'an unchanged sync must not notify').to.be.false;
645
654
  });
646
655
  it('should not offer the walkthrough of a plugin restricted by workspace trust', () => {
647
656
  (0, chai_1.expect)(service.getWalkthrough(wtId), 'offered while the workspace is trusted').to.not.be.undefined;
648
657
  mockDisabledByTrust.add(pluginId);
649
- service.syncWalkthroughsFromPlugins();
658
+ service.syncWalkthroughs();
650
659
  (0, chai_1.expect)(service.getWalkthroughs()).to.be.empty;
651
660
  });
652
661
  it('should offer it again once the workspace is trusted', () => {
653
662
  mockDisabledByTrust.add(pluginId);
654
- service.syncWalkthroughsFromPlugins();
663
+ service.syncWalkthroughs();
655
664
  (0, chai_1.expect)(service.getWalkthroughs()).to.be.empty;
656
665
  mockDisabledByTrust.delete(pluginId);
657
- service.syncWalkthroughsFromPlugins();
666
+ service.syncWalkthroughs();
658
667
  (0, chai_1.expect)(service.getWalkthrough(wtId)).to.not.be.undefined;
659
668
  });
660
669
  it('should close a restricted walkthrough that was open', () => {
661
670
  service.selectWalkthrough(wtId);
662
671
  (0, chai_1.expect)(service.selectedWalkthrough?.id).to.equal(wtId);
663
672
  mockDisabledByTrust.add(pluginId);
664
- service.syncWalkthroughsFromPlugins();
673
+ service.syncWalkthroughs();
665
674
  (0, chai_1.expect)(service.selectedWalkthrough).to.be.undefined;
666
675
  });
667
676
  it('should drop the walkthrough once the plugin is out of sync', () => {
668
677
  // Theia keeps an uninstalled or disabled plugin loaded until the next reload, flagged as out of sync.
669
678
  mockPlugins[0].outOfSync = true;
670
- service.syncWalkthroughsFromPlugins();
679
+ service.syncWalkthroughs();
671
680
  (0, chai_1.expect)(service.getWalkthrough(wtId)).to.be.undefined;
672
681
  (0, chai_1.expect)(service.getWalkthroughs()).to.be.empty;
673
682
  });
@@ -675,17 +684,124 @@ describe('WalkthroughService', () => {
675
684
  let fired = false;
676
685
  service.onDidChangeWalkthroughs(() => { fired = true; });
677
686
  mockPlugins[0].outOfSync = true;
678
- service.syncWalkthroughsFromPlugins();
687
+ service.syncWalkthroughs();
679
688
  (0, chai_1.expect)(fired).to.be.true;
680
689
  });
681
690
  it('should close the walkthrough if it was open', () => {
682
691
  service.selectWalkthrough(wtId);
683
692
  (0, chai_1.expect)(service.selectedWalkthrough?.id).to.equal(wtId);
684
693
  mockPlugins[0].outOfSync = true;
685
- service.syncWalkthroughsFromPlugins();
694
+ service.syncWalkthroughs();
686
695
  (0, chai_1.expect)(service.selectedWalkthrough).to.be.undefined;
687
696
  });
688
697
  });
698
+ describe('contributed walkthroughs', () => {
699
+ const providedId = 'theia.test-walkthrough';
700
+ const pluginId = 'test.publisher.test-plugin';
701
+ const pluginWalkthroughId = 'test.publisher.test-plugin.test-walkthrough';
702
+ function createDefinition(overrides) {
703
+ return {
704
+ id: providedId,
705
+ title: 'Provided Walkthrough',
706
+ description: 'Contributed by a Theia extension',
707
+ icon: 'sparkle',
708
+ steps: [
709
+ { id: 'first', title: 'First', description: 'First step' },
710
+ { id: 'second', title: 'Second', description: 'Second step' }
711
+ ],
712
+ ...overrides
713
+ };
714
+ }
715
+ function provide(...definitions) {
716
+ mockWalkthroughProviders = [{ getWalkthroughs: () => definitions }];
717
+ }
718
+ it('should register the walkthroughs of a provider under their own id', () => {
719
+ provide(createDefinition());
720
+ service.syncWalkthroughs();
721
+ const walkthrough = service.getWalkthrough(providedId);
722
+ (0, chai_1.expect)(walkthrough).to.not.be.undefined;
723
+ (0, chai_1.expect)(walkthrough.title).to.equal('Provided Walkthrough');
724
+ (0, chai_1.expect)(walkthrough.icon).to.equal('sparkle');
725
+ (0, chai_1.expect)(walkthrough.pluginId).to.be.undefined;
726
+ (0, chai_1.expect)(walkthrough.steps.map(step => step.isComplete)).to.deep.equal([false, false]);
727
+ });
728
+ it('should keep plugin and provided walkthroughs alongside each other', () => {
729
+ mockPlugins = [{ model: { id: pluginId, publisher: 'test.publisher', name: 'test-plugin' } }];
730
+ mockDeployedPlugins.set(pluginId, { contributes: { walkthroughs: [createContribution()] } });
731
+ provide(createDefinition());
732
+ service.syncWalkthroughs();
733
+ (0, chai_1.expect)(service.getWalkthroughs().map(walkthrough => walkthrough.id)).to.have.members([pluginWalkthroughId, providedId]);
734
+ });
735
+ it('should not drop a provided walkthrough when plugins change', async () => {
736
+ provide(createDefinition());
737
+ service.syncWalkthroughs();
738
+ mockPlugins = [{ model: { id: pluginId, publisher: 'test.publisher', name: 'test-plugin' } }];
739
+ mockDeployedPlugins.set(pluginId, { contributes: { walkthroughs: [createContribution()] } });
740
+ await service.handlePluginsChanged();
741
+ (0, chai_1.expect)(service.getWalkthrough(providedId)).to.not.be.undefined;
742
+ });
743
+ it('should keep the progress of a re-synced, unchanged walkthrough', async () => {
744
+ provide(createDefinition());
745
+ service.syncWalkthroughs();
746
+ await service.markStepComplete(providedId, 'first');
747
+ service.syncWalkthroughs();
748
+ (0, chai_1.expect)(service.getWalkthrough(providedId).steps[0].isComplete).to.be.true;
749
+ });
750
+ it('should re-register a walkthrough whose definition changed', () => {
751
+ provide(createDefinition());
752
+ service.syncWalkthroughs();
753
+ (0, chai_1.expect)(service.getWalkthrough(providedId).steps).to.have.lengthOf(2);
754
+ provide(createDefinition({ steps: [{ id: 'only', title: 'Only', description: 'Only step' }] }));
755
+ service.syncWalkthroughs();
756
+ (0, chai_1.expect)(service.getWalkthrough(providedId).steps).to.have.lengthOf(1);
757
+ });
758
+ it('should drop a walkthrough that is no longer provided', () => {
759
+ provide(createDefinition());
760
+ service.syncWalkthroughs();
761
+ provide();
762
+ service.syncWalkthroughs();
763
+ (0, chai_1.expect)(service.getWalkthrough(providedId)).to.be.undefined;
764
+ });
765
+ it('should keep the other walkthroughs when a provider throws', () => {
766
+ mockWalkthroughProviders = [
767
+ { getWalkthroughs: () => { throw new Error('provider is broken'); } },
768
+ { getWalkthroughs: () => [createDefinition()] }
769
+ ];
770
+ service.syncWalkthroughs();
771
+ (0, chai_1.expect)(service.getWalkthrough(providedId)).to.not.be.undefined;
772
+ });
773
+ it('should not reject the sync when a provider throws', async () => {
774
+ mockWalkthroughProviders = [{ getWalkthroughs: () => { throw new Error('provider is broken'); } }];
775
+ service.init();
776
+ // A rejected `progressReady` would stop every later sync, so it has to survive a failing provider.
777
+ await service.progressReady;
778
+ });
779
+ it('should ignore a walkthrough whose id is already taken', () => {
780
+ mockWalkthroughProviders = [
781
+ { getWalkthroughs: () => [createDefinition({ title: 'First' })] },
782
+ { getWalkthroughs: () => [createDefinition({ title: 'Second' })] }
783
+ ];
784
+ service.syncWalkthroughs();
785
+ (0, chai_1.expect)(service.getWalkthroughs()).to.have.lengthOf(1);
786
+ (0, chai_1.expect)(service.getWalkthrough(providedId).title).to.equal('First');
787
+ });
788
+ it('should re-sync when a provider signals a change', async () => {
789
+ const onDidChangeEmitter = new event_1.Emitter();
790
+ let definitions = [];
791
+ mockWalkthroughProviders = [{
792
+ getWalkthroughs: () => definitions,
793
+ onDidChange: onDidChangeEmitter.event
794
+ }];
795
+ service.init();
796
+ await service.progressReady;
797
+ (0, chai_1.expect)(service.getWalkthroughs()).to.be.empty;
798
+ definitions = [createDefinition()];
799
+ onDidChangeEmitter.fire();
800
+ await service.progressReady;
801
+ (0, chai_1.expect)(service.getWalkthrough(providedId)).to.not.be.undefined;
802
+ onDidChangeEmitter.dispose();
803
+ });
804
+ });
689
805
  describe('extensionInstalled completion event', () => {
690
806
  const walkthroughContribution = createContribution({
691
807
  steps: [
@@ -705,13 +821,13 @@ describe('WalkthroughService', () => {
705
821
  });
706
822
  }
707
823
  beforeEach(() => {
708
- // Set up the plugin so the walkthrough is discovered via syncWalkthroughsFromPlugins
824
+ // Set up the plugin so the walkthrough is discovered via syncWalkthroughs
709
825
  setupPluginWithWalkthrough();
710
- service.syncWalkthroughsFromPlugins();
826
+ service.syncWalkthroughs();
711
827
  // Wire up the onDidChangePlugins handler manually (simulating @postConstruct)
712
828
  service.toDispose.push(service.pluginSupport.onDidChangePlugins(() => {
713
829
  const previousIds = new Set(service.knownPluginIds);
714
- service.syncWalkthroughsFromPlugins();
830
+ service.syncWalkthroughs();
715
831
  const newIds = new Set(service.pluginSupport.plugins.map((p) => p.model.id));
716
832
  for (const newId of newIds) {
717
833
  if (!previousIds.has(newId)) {
@@ -764,7 +880,7 @@ describe('WalkthroughService', () => {
764
880
  });
765
881
  describe('onContext completion event', () => {
766
882
  beforeEach(() => {
767
- service.registerWalkthrough(createContribution({
883
+ registerContribution(createContribution({
768
884
  steps: [
769
885
  {
770
886
  id: 'context-step',
@@ -827,14 +943,14 @@ describe('WalkthroughService', () => {
827
943
  describe('onContext expressions', () => {
828
944
  it('should complete a step whose context expression already holds at registration', () => {
829
945
  mockContextKeyMatchResult = true;
830
- service.registerWalkthrough(createContribution({
946
+ registerContribution(createContribution({
831
947
  steps: [{ id: 'static', title: 'Static', description: 'd', completionEvents: ['onContext:isLinux'] }]
832
948
  }));
833
949
  const wtId = 'test.publisher.test-plugin.test-walkthrough';
834
950
  (0, chai_1.expect)(service.getWalkthrough(wtId).steps[0].isComplete).to.be.true;
835
951
  });
836
952
  it('should react to a context expression rather than treating it as a single key', () => {
837
- service.registerWalkthrough(createContribution({
953
+ registerContribution(createContribution({
838
954
  steps: [{ id: 'expr', title: 'Expression', description: 'd', completionEvents: ['onContext:foo && bar'] }]
839
955
  }));
840
956
  // The keys of the expression have to be recognised, not the expression string itself.
@@ -848,7 +964,7 @@ describe('WalkthroughService', () => {
848
964
  });
849
965
  describe('onView completion event', () => {
850
966
  beforeEach(() => {
851
- service.registerWalkthrough(createContribution({
967
+ registerContribution(createContribution({
852
968
  steps: [
853
969
  {
854
970
  id: 'view-step',
@@ -879,7 +995,7 @@ describe('WalkthroughService', () => {
879
995
  });
880
996
  it('should handle multiple onView completion events on different steps', async () => {
881
997
  // Register a second walkthrough with a different view event
882
- service.registerWalkthrough(createContribution({
998
+ registerContribution(createContribution({
883
999
  id: 'multi-view-walkthrough',
884
1000
  steps: [
885
1001
  {
@@ -911,7 +1027,7 @@ describe('WalkthroughService', () => {
911
1027
  });
912
1028
  describe('onLink completion event', () => {
913
1029
  beforeEach(() => {
914
- service.registerWalkthrough(createContribution({
1030
+ registerContribution(createContribution({
915
1031
  steps: [
916
1032
  {
917
1033
  id: 'link-step',
@@ -939,7 +1055,7 @@ describe('WalkthroughService', () => {
939
1055
  (0, chai_1.expect)(mockOpenerOpenCalls).to.have.lengthOf(1);
940
1056
  });
941
1057
  it('should complete step with command: URI', async () => {
942
- service.registerWalkthrough(createContribution({
1058
+ registerContribution(createContribution({
943
1059
  id: 'cmd-link-walkthrough',
944
1060
  steps: [
945
1061
  {
@@ -989,7 +1105,7 @@ describe('WalkthroughService', () => {
989
1105
  // Wire up the onDidChangePlugins handler manually
990
1106
  service.toDispose.push(service.pluginSupport.onDidChangePlugins(() => {
991
1107
  const previousIds = new Set(service.knownPluginIds);
992
- service.syncWalkthroughsFromPlugins();
1108
+ service.syncWalkthroughs();
993
1109
  const newIds = new Set(service.pluginSupport.plugins.map((p) => p.model.id));
994
1110
  for (const newId of newIds) {
995
1111
  if (!previousIds.has(newId)) {