@theia/getting-started 1.75.0-next.21 → 1.75.0-next.28

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 (86) hide show
  1. package/lib/browser/getting-started-contribution.d.ts +30 -2
  2. package/lib/browser/getting-started-contribution.d.ts.map +1 -1
  3. package/lib/browser/getting-started-contribution.js +120 -0
  4. package/lib/browser/getting-started-contribution.js.map +1 -1
  5. package/lib/browser/getting-started-contribution.spec.d.ts +2 -0
  6. package/lib/browser/getting-started-contribution.spec.d.ts.map +1 -0
  7. package/lib/browser/getting-started-contribution.spec.js +152 -0
  8. package/lib/browser/getting-started-contribution.spec.js.map +1 -0
  9. package/lib/browser/getting-started-frontend-module.d.ts.map +1 -1
  10. package/lib/browser/getting-started-frontend-module.js +8 -0
  11. package/lib/browser/getting-started-frontend-module.js.map +1 -1
  12. package/lib/browser/getting-started-widget.d.ts +19 -0
  13. package/lib/browser/getting-started-widget.d.ts.map +1 -1
  14. package/lib/browser/getting-started-widget.js +57 -2
  15. package/lib/browser/getting-started-widget.js.map +1 -1
  16. package/lib/browser/getting-started-widget.spec.d.ts +2 -0
  17. package/lib/browser/getting-started-widget.spec.d.ts.map +1 -0
  18. package/lib/browser/getting-started-widget.spec.js +71 -0
  19. package/lib/browser/getting-started-widget.spec.js.map +1 -0
  20. package/lib/browser/walkthrough-card.d.ts +8 -0
  21. package/lib/browser/walkthrough-card.d.ts.map +1 -0
  22. package/lib/browser/walkthrough-card.js +40 -0
  23. package/lib/browser/walkthrough-card.js.map +1 -0
  24. package/lib/browser/walkthrough-card.spec.d.ts +2 -0
  25. package/lib/browser/walkthrough-card.spec.d.ts.map +1 -0
  26. package/lib/browser/walkthrough-card.spec.js +188 -0
  27. package/lib/browser/walkthrough-card.spec.js.map +1 -0
  28. package/lib/browser/walkthrough-detail.d.ts +19 -0
  29. package/lib/browser/walkthrough-detail.d.ts.map +1 -0
  30. package/lib/browser/walkthrough-detail.js +210 -0
  31. package/lib/browser/walkthrough-detail.js.map +1 -0
  32. package/lib/browser/walkthrough-detail.spec.d.ts +2 -0
  33. package/lib/browser/walkthrough-detail.spec.d.ts.map +1 -0
  34. package/lib/browser/walkthrough-detail.spec.js +409 -0
  35. package/lib/browser/walkthrough-detail.spec.js.map +1 -0
  36. package/lib/browser/walkthrough-icon.d.ts +10 -0
  37. package/lib/browser/walkthrough-icon.d.ts.map +1 -0
  38. package/lib/browser/walkthrough-icon.js +37 -0
  39. package/lib/browser/walkthrough-icon.js.map +1 -0
  40. package/lib/browser/walkthrough-section.d.ts +23 -0
  41. package/lib/browser/walkthrough-section.d.ts.map +1 -0
  42. package/lib/browser/walkthrough-section.js +65 -0
  43. package/lib/browser/walkthrough-section.js.map +1 -0
  44. package/lib/browser/walkthrough-section.spec.d.ts +2 -0
  45. package/lib/browser/walkthrough-section.spec.d.ts.map +1 -0
  46. package/lib/browser/walkthrough-section.spec.js +328 -0
  47. package/lib/browser/walkthrough-section.spec.js.map +1 -0
  48. package/lib/browser/walkthrough-service.d.ts +166 -0
  49. package/lib/browser/walkthrough-service.d.ts.map +1 -0
  50. package/lib/browser/walkthrough-service.js +505 -0
  51. package/lib/browser/walkthrough-service.js.map +1 -0
  52. package/lib/browser/walkthrough-service.spec.d.ts +2 -0
  53. package/lib/browser/walkthrough-service.spec.d.ts.map +1 -0
  54. package/lib/browser/walkthrough-service.spec.js +1026 -0
  55. package/lib/browser/walkthrough-service.spec.js.map +1 -0
  56. package/lib/common/getting-started-preferences.d.ts +1 -0
  57. package/lib/common/getting-started-preferences.d.ts.map +1 -1
  58. package/lib/common/getting-started-preferences.js +5 -0
  59. package/lib/common/getting-started-preferences.js.map +1 -1
  60. package/lib/common/walkthrough-commands.d.ts +11 -0
  61. package/lib/common/walkthrough-commands.d.ts.map +1 -0
  62. package/lib/common/walkthrough-commands.js +41 -0
  63. package/lib/common/walkthrough-commands.js.map +1 -0
  64. package/lib/common/walkthrough-types.d.ts +22 -0
  65. package/lib/common/walkthrough-types.d.ts.map +1 -0
  66. package/lib/common/walkthrough-types.js +18 -0
  67. package/lib/common/walkthrough-types.js.map +1 -0
  68. package/package.json +8 -7
  69. package/src/browser/getting-started-contribution.spec.ts +188 -0
  70. package/src/browser/getting-started-contribution.ts +139 -3
  71. package/src/browser/getting-started-frontend-module.ts +8 -0
  72. package/src/browser/getting-started-widget.spec.tsx +88 -0
  73. package/src/browser/getting-started-widget.tsx +70 -3
  74. package/src/browser/style/index.css +279 -0
  75. package/src/browser/walkthrough-card.spec.tsx +247 -0
  76. package/src/browser/walkthrough-card.tsx +62 -0
  77. package/src/browser/walkthrough-detail.spec.tsx +675 -0
  78. package/src/browser/walkthrough-detail.tsx +347 -0
  79. package/src/browser/walkthrough-icon.tsx +42 -0
  80. package/src/browser/walkthrough-section.spec.tsx +484 -0
  81. package/src/browser/walkthrough-section.tsx +119 -0
  82. package/src/browser/walkthrough-service.spec.ts +1279 -0
  83. package/src/browser/walkthrough-service.ts +565 -0
  84. package/src/common/getting-started-preferences.ts +6 -0
  85. package/src/common/walkthrough-commands.ts +37 -0
  86. package/src/common/walkthrough-types.ts +39 -0
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ // *****************************************************************************
3
+ // Copyright (C) 2026 EclipseSource and others.
4
+ //
5
+ // This program and the accompanying materials are made available under the
6
+ // terms of the Eclipse Public License v. 2.0 which is available at
7
+ // http://www.eclipse.org/legal/epl-2.0.
8
+ //
9
+ // This Source Code may also be made available under the following Secondary
10
+ // Licenses when the conditions for such availability set forth in the Eclipse
11
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
+ // with the GNU Classpath Exception which is available at
13
+ // https://www.gnu.org/software/classpath/license.html.
14
+ //
15
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16
+ // *****************************************************************************
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.WalkthroughDetail = WalkthroughDetail;
19
+ const browser_1 = require("@theia/core/lib/browser");
20
+ const disposable_1 = require("@theia/core/lib/common/disposable");
21
+ const nls_1 = require("@theia/core/lib/common/nls");
22
+ const React = require("@theia/core/shared/react");
23
+ const plugin_shared_style_1 = require("@theia/plugin-ext/lib/main/browser/plugin-shared-style");
24
+ const walkthrough_icon_1 = require("./walkthrough-icon");
25
+ function WalkthroughDetail(props) {
26
+ const { walkthrough, onStepSelect, onBack, selectedStep, markdownRenderer, themeService } = props;
27
+ return (React.createElement("div", { className: 'gs-walkthrough-detail' },
28
+ React.createElement("div", { className: 'gs-walkthrough-detail-header' },
29
+ React.createElement("a", { role: 'button', tabIndex: 0, className: 'gs-walkthrough-back-link', onClick: onBack, onKeyDown: (e) => {
30
+ if (e.key === 'Enter') {
31
+ onBack();
32
+ }
33
+ } },
34
+ React.createElement("i", { className: (0, browser_1.codicon)('arrow-left') }),
35
+ React.createElement("span", { className: 'gs-walkthrough-back-label' }, nls_1.nls.localizeByDefault('Back'))),
36
+ React.createElement("h2", { className: 'gs-walkthrough-detail-title' },
37
+ React.createElement(walkthrough_icon_1.WalkthroughIcon, { walkthrough: walkthrough }),
38
+ walkthrough.title),
39
+ React.createElement("p", { className: 'gs-walkthrough-detail-description' }, walkthrough.description)),
40
+ React.createElement("div", { className: 'gs-walkthrough-detail-body' },
41
+ React.createElement("div", { className: 'gs-walkthrough-steps' },
42
+ walkthrough.steps.map(step => (React.createElement(WalkthroughStepItem, { key: step.id, step: step, isSelected: selectedStep?.id === step.id, onSelect: onStepSelect, onToggleDone: props.onToggleStepDone }))),
43
+ props.onMarkAllStepsDone && (React.createElement("button", { className: 'theia-button secondary gs-walkthrough-mark-all-done', disabled: walkthrough.steps.every(step => step.isComplete), onClick: props.onMarkAllStepsDone }, nls_1.nls.localizeByDefault('Mark Done')))),
44
+ selectedStep && (React.createElement("div", { className: 'gs-walkthrough-step-content' },
45
+ React.createElement(WalkthroughStepContent, { step: selectedStep, markdownRenderer: markdownRenderer, onLinkClick: props.onLinkClick, themeService: themeService, logger: props.logger }))))));
46
+ }
47
+ function WalkthroughStepItem(props) {
48
+ const { step, isSelected, onSelect, onToggleDone } = props;
49
+ const iconClass = step.isComplete ? (0, browser_1.codicon)('pass-filled') : (0, browser_1.codicon)('circle-large-outline');
50
+ const toggleDone = (event) => {
51
+ // Completing a step is a separate action from opening it.
52
+ event.stopPropagation();
53
+ onToggleDone?.(step);
54
+ };
55
+ return (React.createElement("div", { className: `gs-walkthrough-step-item ${isSelected ? 'selected' : ''} ${step.isComplete ? 'completed' : ''}`, role: 'button', tabIndex: 0, onClick: () => onSelect(step), onKeyDown: (e) => {
56
+ if (e.key === 'Enter') {
57
+ onSelect(step);
58
+ }
59
+ } },
60
+ React.createElement("span", { className: `gs-walkthrough-step-icon ${iconClass}`, role: onToggleDone ? 'checkbox' : undefined, "aria-checked": onToggleDone ? step.isComplete : undefined, tabIndex: onToggleDone ? 0 : undefined, title: onToggleDone
61
+ ? (step.isComplete
62
+ ? nls_1.nls.localize('theia/getting-started/markStepUndone', 'Mark Undone')
63
+ : nls_1.nls.localizeByDefault('Mark Done'))
64
+ : undefined, onClick: onToggleDone && toggleDone, onKeyDown: onToggleDone && ((e) => {
65
+ if (e.key === 'Enter' || e.key === ' ') {
66
+ toggleDone(e);
67
+ }
68
+ }) }),
69
+ React.createElement("span", { className: 'gs-walkthrough-step-title' }, step.title)));
70
+ }
71
+ function WalkthroughStepContent(props) {
72
+ const { step, markdownRenderer, themeService } = props;
73
+ return (React.createElement("div", { className: 'gs-walkthrough-step-detail' },
74
+ React.createElement("h3", null, step.title),
75
+ React.createElement(WalkthroughDescriptionContent, { description: step.description, markdownRenderer: markdownRenderer, onLinkClick: props.onLinkClick }),
76
+ step.media && renderMedia(step.media, markdownRenderer, themeService, props.logger, props.onLinkClick)));
77
+ }
78
+ function WalkthroughDescriptionContent(props) {
79
+ // eslint-disable-next-line no-null/no-null
80
+ const containerRef = React.useRef(null);
81
+ const renderResultRef = React.useRef();
82
+ const disposablesRef = React.useRef();
83
+ const onLinkClickRef = React.useRef(props.onLinkClick);
84
+ onLinkClickRef.current = props.onLinkClick;
85
+ React.useEffect(() => {
86
+ if (!containerRef.current) {
87
+ return;
88
+ }
89
+ renderResultRef.current?.dispose();
90
+ disposablesRef.current?.dispose();
91
+ const options = createMarkdownOptions(onLinkClickRef, disposablesRef);
92
+ const result = props.markdownRenderer.render(toTrustedMarkdown(toMarkdownWithLineBreaks(props.description)), options);
93
+ renderResultRef.current = result;
94
+ containerRef.current.innerHTML = '';
95
+ containerRef.current.appendChild(result.element);
96
+ return () => {
97
+ renderResultRef.current?.dispose();
98
+ disposablesRef.current?.dispose();
99
+ };
100
+ }, [props.description, props.markdownRenderer]);
101
+ return React.createElement("div", { className: 'gs-walkthrough-step-description', ref: containerRef });
102
+ }
103
+ /**
104
+ * Walkthrough content is rendered as trusted Markdown so that the `command:` links it relies on survive
105
+ * rendering. It comes from an installed plugin, which can execute commands through its own API anyway.
106
+ */
107
+ function toTrustedMarkdown(value) {
108
+ return { value, isTrusted: true };
109
+ }
110
+ /**
111
+ * Routes link activation to `onLinkClick`. Without an action handler the Monaco based renderer clears every
112
+ * `href` and installs no click handling, which leaves the links of the rendered content inert.
113
+ */
114
+ function createMarkdownOptions(onLinkClickRef, disposablesRef) {
115
+ if (!onLinkClickRef.current) {
116
+ return undefined;
117
+ }
118
+ const disposables = new disposable_1.DisposableCollection();
119
+ disposablesRef.current = disposables;
120
+ return {
121
+ actionHandler: {
122
+ callback: (content) => onLinkClickRef.current?.(content),
123
+ disposables
124
+ }
125
+ };
126
+ }
127
+ /**
128
+ * Turn the single newlines that walkthrough step descriptions use for line breaks into Markdown hard breaks.
129
+ *
130
+ * VS Code renders every line of a step description as its own block, while Markdown collapses single
131
+ * newlines into spaces. Blank lines are left alone so that explicit paragraphs still work.
132
+ */
133
+ function toMarkdownWithLineBreaks(description) {
134
+ return description.replace(/(?<!\n)\n(?!\n)/g, ' \n');
135
+ }
136
+ function renderMedia(media, markdownRenderer, themeService, logger, onLinkClick) {
137
+ if (!media) {
138
+ return undefined;
139
+ }
140
+ if ('markdown' in media) {
141
+ return React.createElement(WalkthroughMedia, { src: media.markdown, markdownRenderer: markdownRenderer, logger: logger, onLinkClick: onLinkClick });
142
+ }
143
+ if ('svg' in media) {
144
+ return React.createElement(WalkthroughMediaImage, { src: media.svg, altText: media.altText || '', themeService: themeService });
145
+ }
146
+ if ('image' in media) {
147
+ return React.createElement(WalkthroughMediaImage, { src: media.image, altText: media.altText || '', themeService: themeService });
148
+ }
149
+ return undefined;
150
+ }
151
+ function pickThemeVariant(source, themeType) {
152
+ if (typeof source === 'string') {
153
+ return source;
154
+ }
155
+ switch (themeType) {
156
+ case 'light': return source.light;
157
+ case 'hc': return source.hc;
158
+ case 'hcLight': return source.hcLight;
159
+ default: return source.dark;
160
+ }
161
+ }
162
+ function WalkthroughMediaImage(props) {
163
+ const { themeService } = props;
164
+ const [themeType, setThemeType] = React.useState(() => themeService.getCurrentTheme().type);
165
+ const [failed, setFailed] = React.useState(false);
166
+ React.useEffect(() => {
167
+ const disposable = themeService.onDidColorThemeChange(event => setThemeType(event.newTheme.type));
168
+ return () => disposable.dispose();
169
+ }, [themeService]);
170
+ const src = plugin_shared_style_1.PluginSharedStyle.toExternalIconUrl(pickThemeVariant(props.src, themeType));
171
+ React.useEffect(() => setFailed(false), [src]);
172
+ if (failed) {
173
+ // A missing image must not leave a broken image placeholder behind.
174
+ return undefined;
175
+ }
176
+ return React.createElement("img", { className: 'gs-walkthrough-media-image', src: src, alt: props.altText, onError: () => setFailed(true) });
177
+ }
178
+ function WalkthroughMedia(props) {
179
+ // eslint-disable-next-line no-null/no-null
180
+ const containerRef = React.useRef(null);
181
+ const renderResultRef = React.useRef();
182
+ const disposablesRef = React.useRef();
183
+ const onLinkClickRef = React.useRef(props.onLinkClick);
184
+ onLinkClickRef.current = props.onLinkClick;
185
+ React.useEffect(() => {
186
+ let cancelled = false;
187
+ fetch(plugin_shared_style_1.PluginSharedStyle.toExternalIconUrl(props.src))
188
+ .then(response => !cancelled && response.ok ? response.text() : '')
189
+ .then(text => {
190
+ if (!cancelled && containerRef.current && text) {
191
+ renderResultRef.current?.dispose();
192
+ disposablesRef.current?.dispose();
193
+ const options = createMarkdownOptions(onLinkClickRef, disposablesRef);
194
+ const result = props.markdownRenderer.render(toTrustedMarkdown(text), options);
195
+ renderResultRef.current = result;
196
+ containerRef.current.innerHTML = '';
197
+ containerRef.current.appendChild(result.element);
198
+ }
199
+ })
200
+ .catch(error => props.logger.warn(`Could not load the walkthrough media '${props.src}'.`, error));
201
+ return () => {
202
+ cancelled = true;
203
+ renderResultRef.current?.dispose();
204
+ renderResultRef.current = undefined;
205
+ disposablesRef.current?.dispose();
206
+ };
207
+ }, [props.src, props.markdownRenderer]);
208
+ return React.createElement("div", { className: 'gs-walkthrough-media-markdown', ref: containerRef });
209
+ }
210
+ //# sourceMappingURL=walkthrough-detail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walkthrough-detail.js","sourceRoot":"","sources":["../../src/browser/walkthrough-detail.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=walkthrough-detail.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walkthrough-detail.spec.d.ts","sourceRoot":"","sources":["../../src/browser/walkthrough-detail.spec.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,409 @@
1
+ "use strict";
2
+ // *****************************************************************************
3
+ // Copyright (C) 2026 EclipseSource and others.
4
+ //
5
+ // This program and the accompanying materials are made available under the
6
+ // terms of the Eclipse Public License v. 2.0 which is available at
7
+ // http://www.eclipse.org/legal/epl-2.0.
8
+ //
9
+ // This Source Code may also be made available under the following Secondary
10
+ // Licenses when the conditions for such availability set forth in the Eclipse
11
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
+ // with the GNU Classpath Exception which is available at
13
+ // https://www.gnu.org/software/classpath/license.html.
14
+ //
15
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16
+ // *****************************************************************************
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ const assert = require("assert");
19
+ const React = require("react");
20
+ const jsdom_1 = require("@theia/core/lib/browser/test/jsdom");
21
+ let disableJSDOM = (0, jsdom_1.enableJSDOM)();
22
+ const client_1 = require("react-dom/client");
23
+ const disposable_1 = require("@theia/core/lib/common/disposable");
24
+ const walkthrough_detail_1 = require("./walkthrough-detail");
25
+ function createMockStep(overrides) {
26
+ return {
27
+ id: 'step-1',
28
+ title: 'Test Step',
29
+ description: 'A test step description',
30
+ isComplete: false,
31
+ ...overrides
32
+ };
33
+ }
34
+ function createMockWalkthrough(overrides) {
35
+ return {
36
+ id: 'test-walkthrough',
37
+ title: 'Test Walkthrough',
38
+ description: 'A test walkthrough description',
39
+ steps: [
40
+ createMockStep({ id: 'step-1', title: 'First Step' }),
41
+ createMockStep({ id: 'step-2', title: 'Second Step' }),
42
+ createMockStep({ id: 'step-3', title: 'Third Step' })
43
+ ],
44
+ pluginId: 'test.plugin',
45
+ ...overrides
46
+ };
47
+ }
48
+ function createMockMarkdownRenderer() {
49
+ return {
50
+ render: (markdown) => {
51
+ const div = document.createElement('div');
52
+ div.textContent = markdown?.value ?? 'rendered markdown';
53
+ return { element: div, dispose: () => { } };
54
+ }
55
+ };
56
+ }
57
+ function createMockThemeService(themeType = 'dark') {
58
+ return {
59
+ getCurrentTheme: () => ({ id: 'test', label: 'Test', type: themeType }),
60
+ onDidColorThemeChange: () => disposable_1.Disposable.NULL
61
+ };
62
+ }
63
+ function createMockLogger() {
64
+ return { warn: () => { }, error: () => { }, info: () => { }, debug: () => { } };
65
+ }
66
+ describe('WalkthroughDetail', () => {
67
+ let container;
68
+ let root;
69
+ let mockRenderer;
70
+ before(() => disableJSDOM = (0, jsdom_1.enableJSDOM)());
71
+ after(() => disableJSDOM());
72
+ beforeEach(() => {
73
+ container = document.createElement('div');
74
+ document.body.appendChild(container);
75
+ root = (0, client_1.createRoot)(container);
76
+ mockRenderer = createMockMarkdownRenderer();
77
+ });
78
+ afterEach(() => {
79
+ root.unmount();
80
+ document.body.removeChild(container);
81
+ });
82
+ it('should render walkthrough title and back button', done => {
83
+ const walkthrough = createMockWalkthrough({ title: 'Detail Walkthrough' });
84
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { }, markdownRenderer: mockRenderer }));
85
+ setTimeout(() => {
86
+ const title = container.querySelector('.gs-walkthrough-detail-title');
87
+ assert.ok(title, 'Title element should exist');
88
+ assert.ok(title?.textContent?.includes('Detail Walkthrough'), 'Should contain walkthrough title');
89
+ const backLink = container.querySelector('.gs-walkthrough-back-link');
90
+ assert.ok(backLink, 'Back link should exist');
91
+ // The label is a separate element so that it, and not the arrow icon, can carry the hover underline.
92
+ assert.ok(backLink?.querySelector('.gs-walkthrough-back-label'), 'Back label should be its own element');
93
+ done();
94
+ }, 50);
95
+ });
96
+ it('should render all steps in the step list', done => {
97
+ const walkthrough = createMockWalkthrough({
98
+ steps: [
99
+ createMockStep({ id: 's1', title: 'Step A' }),
100
+ createMockStep({ id: 's2', title: 'Step B' }),
101
+ createMockStep({ id: 's3', title: 'Step C' })
102
+ ]
103
+ });
104
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { }, markdownRenderer: mockRenderer }));
105
+ setTimeout(() => {
106
+ const stepItems = container.querySelectorAll('.gs-walkthrough-step-item');
107
+ assert.strictEqual(stepItems.length, 3, 'Should render 3 step items');
108
+ const stepTitles = container.querySelectorAll('.gs-walkthrough-step-title');
109
+ assert.ok(stepTitles[0]?.textContent?.includes('Step A'), 'First step should be Step A');
110
+ assert.ok(stepTitles[1]?.textContent?.includes('Step B'), 'Second step should be Step B');
111
+ assert.ok(stepTitles[2]?.textContent?.includes('Step C'), 'Third step should be Step C');
112
+ done();
113
+ }, 50);
114
+ });
115
+ it('should highlight the selected step', done => {
116
+ const selectedStep = createMockStep({ id: 's2', title: 'Selected Step' });
117
+ const walkthrough = createMockWalkthrough({
118
+ steps: [
119
+ createMockStep({ id: 's1', title: 'Step A' }),
120
+ selectedStep,
121
+ createMockStep({ id: 's3', title: 'Step C' })
122
+ ]
123
+ });
124
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { }, selectedStep: selectedStep, markdownRenderer: mockRenderer }));
125
+ setTimeout(() => {
126
+ const stepItems = container.querySelectorAll('.gs-walkthrough-step-item');
127
+ assert.ok(!stepItems[0].classList.contains('selected'), 'First step should not be selected');
128
+ assert.ok(stepItems[1].classList.contains('selected'), 'Second step should be selected');
129
+ assert.ok(!stepItems[2].classList.contains('selected'), 'Third step should not be selected');
130
+ done();
131
+ }, 50);
132
+ });
133
+ it('should call onBack when back button is clicked', done => {
134
+ const walkthrough = createMockWalkthrough();
135
+ let backCalled = false;
136
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { backCalled = true; }, markdownRenderer: mockRenderer }));
137
+ setTimeout(() => {
138
+ const backLink = container.querySelector('.gs-walkthrough-back-link');
139
+ assert.ok(backLink, 'Back link should exist');
140
+ backLink.click();
141
+ setTimeout(() => {
142
+ assert.ok(backCalled, 'onBack should have been called');
143
+ done();
144
+ }, 50);
145
+ }, 50);
146
+ });
147
+ it('should call onStepSelect when a step is clicked', done => {
148
+ const steps = [
149
+ createMockStep({ id: 's1', title: 'Step A' }),
150
+ createMockStep({ id: 's2', title: 'Step B' })
151
+ ];
152
+ const walkthrough = createMockWalkthrough({ steps });
153
+ let selectedStep;
154
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: step => { selectedStep = step; }, onBack: () => { }, markdownRenderer: mockRenderer }));
155
+ setTimeout(() => {
156
+ const stepItems = container.querySelectorAll('.gs-walkthrough-step-item');
157
+ stepItems[1].click();
158
+ setTimeout(() => {
159
+ assert.ok(selectedStep, 'onStepSelect should have been called');
160
+ assert.strictEqual(selectedStep?.id, 's2', 'Should select the clicked step');
161
+ done();
162
+ }, 50);
163
+ }, 50);
164
+ });
165
+ it('should render step content for the selected step', done => {
166
+ const selectedStep = createMockStep({
167
+ id: 's1',
168
+ title: 'Content Step',
169
+ description: 'Step with content'
170
+ });
171
+ const walkthrough = createMockWalkthrough({ steps: [selectedStep] });
172
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { }, selectedStep: selectedStep, markdownRenderer: mockRenderer }));
173
+ setTimeout(() => {
174
+ const stepContent = container.querySelector('.gs-walkthrough-step-content');
175
+ assert.ok(stepContent, 'Step content section should exist');
176
+ const stepDetail = container.querySelector('.gs-walkthrough-step-detail');
177
+ assert.ok(stepDetail, 'Step detail should exist');
178
+ assert.ok(stepDetail?.textContent?.includes('Content Step'), 'Should show step title');
179
+ const descriptionContainer = container.querySelector('.gs-walkthrough-step-description');
180
+ assert.ok(descriptionContainer, 'Description container should exist');
181
+ assert.ok(descriptionContainer?.textContent?.includes('Step with content'), 'Should show step description via markdown renderer');
182
+ done();
183
+ }, 50);
184
+ });
185
+ it('should not render step content when no step is selected', done => {
186
+ const walkthrough = createMockWalkthrough();
187
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { }, markdownRenderer: mockRenderer }));
188
+ setTimeout(() => {
189
+ const stepContent = container.querySelector('.gs-walkthrough-step-content');
190
+ // eslint-disable-next-line no-null/no-null
191
+ assert.strictEqual(stepContent, null, 'Step content should not be rendered');
192
+ done();
193
+ }, 50);
194
+ });
195
+ it('should show completion icon for completed steps', done => {
196
+ const walkthrough = createMockWalkthrough({
197
+ steps: [
198
+ createMockStep({ id: 's1', isComplete: true }),
199
+ createMockStep({ id: 's2', isComplete: false })
200
+ ]
201
+ });
202
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { }, markdownRenderer: mockRenderer }));
203
+ setTimeout(() => {
204
+ const stepItems = container.querySelectorAll('.gs-walkthrough-step-item');
205
+ assert.ok(stepItems[0].classList.contains('completed'), 'Completed step should have completed class');
206
+ assert.ok(!stepItems[1].classList.contains('completed'), 'Incomplete step should not have completed class');
207
+ const icons = container.querySelectorAll('.gs-walkthrough-step-icon');
208
+ assert.ok(icons[0].classList.contains('codicon-pass-filled'), 'Completed step should have pass-filled icon');
209
+ assert.ok(icons[1].classList.contains('codicon-circle-large-outline'), 'Incomplete step should have circle-outline icon');
210
+ done();
211
+ }, 50);
212
+ });
213
+ it('should render image media for selected step', done => {
214
+ const selectedStep = createMockStep({
215
+ id: 's1',
216
+ media: { image: '/path/to/image.png', altText: 'test image' }
217
+ });
218
+ const walkthrough = createMockWalkthrough({ steps: [selectedStep] });
219
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { }, selectedStep: selectedStep, markdownRenderer: mockRenderer }));
220
+ setTimeout(() => {
221
+ const img = container.querySelector('.gs-walkthrough-media-image');
222
+ assert.ok(img, 'Image element should exist');
223
+ assert.strictEqual(img.getAttribute('src'), '/path/to/image.png', 'Image src should be set');
224
+ assert.strictEqual(img.getAttribute('alt'), 'test image', 'Image alt should be set');
225
+ done();
226
+ }, 50);
227
+ });
228
+ it('should render SVG media for selected step', done => {
229
+ const selectedStep = createMockStep({
230
+ id: 's1',
231
+ media: { svg: '/path/to/graphic.svg' }
232
+ });
233
+ const walkthrough = createMockWalkthrough({ steps: [selectedStep] });
234
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { }, selectedStep: selectedStep, markdownRenderer: mockRenderer }));
235
+ setTimeout(() => {
236
+ const img = container.querySelector('.gs-walkthrough-media-image');
237
+ assert.ok(img, 'SVG image element should exist');
238
+ assert.strictEqual(img.getAttribute('src'), '/path/to/graphic.svg', 'SVG src should be set');
239
+ done();
240
+ }, 50);
241
+ });
242
+ it('should turn single newlines in a description into Markdown hard breaks', done => {
243
+ const renderedMarkdown = [];
244
+ const recordingRenderer = {
245
+ render: (markdown) => {
246
+ renderedMarkdown.push(markdown.value);
247
+ return { element: document.createElement('div'), dispose: () => { } };
248
+ }
249
+ };
250
+ const selectedStep = createMockStep({
251
+ id: 's1',
252
+ description: 'First line\nSecond line\n\nNew paragraph'
253
+ });
254
+ const walkthrough = createMockWalkthrough({ steps: [selectedStep] });
255
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { }, selectedStep: selectedStep, markdownRenderer: recordingRenderer }));
256
+ setTimeout(() => {
257
+ const description = renderedMarkdown.find(value => value.includes('First line'));
258
+ assert.ok(description, 'The description should have been rendered');
259
+ assert.ok(description?.includes('First line \nSecond line'), 'A single newline should become a hard break');
260
+ assert.ok(description?.includes('Second line\n\nNew paragraph'), 'A blank line should stay a paragraph break');
261
+ done();
262
+ }, 50);
263
+ });
264
+ it('should mark a step done when its step icon is clicked, without selecting the step', done => {
265
+ const step = createMockStep({ id: 's1', isComplete: false });
266
+ const walkthrough = createMockWalkthrough({ steps: [step] });
267
+ let markedStep;
268
+ let selectedStep;
269
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: s => { selectedStep = s; }, onBack: () => { }, markdownRenderer: mockRenderer, onToggleStepDone: s => { markedStep = s; } }));
270
+ setTimeout(() => {
271
+ const icon = container.querySelector('.gs-walkthrough-step-icon');
272
+ assert.ok(icon, 'Step icon should exist');
273
+ icon.click();
274
+ setTimeout(() => {
275
+ assert.strictEqual(markedStep?.id, 's1', 'onToggleStepDone should receive the step');
276
+ assert.strictEqual(selectedStep, undefined, 'Clicking the icon should not select the step');
277
+ done();
278
+ }, 50);
279
+ }, 50);
280
+ });
281
+ it('should toggle a completed step back to pending when its step icon is clicked', done => {
282
+ const step = createMockStep({ id: 's1', isComplete: true });
283
+ const walkthrough = createMockWalkthrough({ steps: [step] });
284
+ let toggledStep;
285
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { }, markdownRenderer: mockRenderer, onToggleStepDone: s => { toggledStep = s; } }));
286
+ setTimeout(() => {
287
+ const icon = container.querySelector('.gs-walkthrough-step-icon');
288
+ assert.strictEqual(icon.getAttribute('aria-checked'), 'true', 'A completed step should read as checked');
289
+ icon.click();
290
+ setTimeout(() => {
291
+ assert.strictEqual(toggledStep?.id, 's1', 'onToggleStepDone should receive the completed step');
292
+ done();
293
+ }, 50);
294
+ }, 50);
295
+ });
296
+ it('should offer a Mark All Done action below the step list', done => {
297
+ const walkthrough = createMockWalkthrough({
298
+ steps: [createMockStep({ id: 's1' }), createMockStep({ id: 's2' })]
299
+ });
300
+ let markedAll = false;
301
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { }, markdownRenderer: mockRenderer, onMarkAllStepsDone: () => { markedAll = true; } }));
302
+ setTimeout(() => {
303
+ const button = container.querySelector('.gs-walkthrough-mark-all-done');
304
+ assert.ok(button, 'Mark All Done button should exist');
305
+ assert.ok(button.closest('.gs-walkthrough-steps'), 'It should sit with the step list');
306
+ assert.ok(!button.disabled, 'It should be enabled while steps are pending');
307
+ button.click();
308
+ setTimeout(() => {
309
+ assert.strictEqual(markedAll, true, 'onMarkAllStepsDone should have been called');
310
+ done();
311
+ }, 50);
312
+ }, 50);
313
+ });
314
+ it('should disable Mark All Done once every step is complete', done => {
315
+ const walkthrough = createMockWalkthrough({
316
+ steps: [createMockStep({ id: 's1', isComplete: true }), createMockStep({ id: 's2', isComplete: true })]
317
+ });
318
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { }, markdownRenderer: mockRenderer, onMarkAllStepsDone: () => { } }));
319
+ setTimeout(() => {
320
+ const button = container.querySelector('.gs-walkthrough-mark-all-done');
321
+ assert.ok(button, 'Mark All Done button should exist');
322
+ assert.ok(button.disabled, 'It should be disabled when nothing is left to complete');
323
+ done();
324
+ }, 50);
325
+ });
326
+ it('should keep `command:` links as plain inline links', done => {
327
+ const selectedStep = createMockStep({ id: 's1', description: '[Do it](command:my.command)' });
328
+ const walkthrough = createMockWalkthrough({ steps: [selectedStep] });
329
+ const linkRenderer = {
330
+ render: () => {
331
+ const div = document.createElement('div');
332
+ const anchor = document.createElement('a');
333
+ anchor.setAttribute('data-href', 'command:my.command');
334
+ anchor.textContent = 'Do it';
335
+ div.appendChild(anchor);
336
+ return { element: div, dispose: () => { } };
337
+ }
338
+ };
339
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { }, selectedStep: selectedStep, markdownRenderer: linkRenderer }));
340
+ setTimeout(() => {
341
+ const anchor = container.querySelector('.gs-walkthrough-step-description a');
342
+ assert.ok(anchor, 'The command link should be rendered');
343
+ assert.ok(!anchor.classList.contains('theia-button'), 'A command link should not be styled as a button');
344
+ done();
345
+ }, 50);
346
+ });
347
+ it('should pick the image variant matching the current theme and resolve the plugin resource', done => {
348
+ const selectedStep = createMockStep({
349
+ id: 's1',
350
+ media: {
351
+ image: {
352
+ dark: 'hostedPlugin/pub_name/dark.png',
353
+ light: 'hostedPlugin/pub_name/light.png',
354
+ hc: 'hostedPlugin/pub_name/hc.png',
355
+ hcLight: 'hostedPlugin/pub_name/hc-light.png'
356
+ },
357
+ altText: 'themed'
358
+ }
359
+ });
360
+ const walkthrough = createMockWalkthrough({ steps: [selectedStep] });
361
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService('light'), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { }, selectedStep: selectedStep, markdownRenderer: mockRenderer }));
362
+ setTimeout(() => {
363
+ const img = container.querySelector('.gs-walkthrough-media-image');
364
+ assert.ok(img, 'Image element should exist');
365
+ const src = img.getAttribute('src');
366
+ assert.ok(src.includes('light.png'), `Should use the light variant, was '${src}'`);
367
+ assert.ok(!src.startsWith('hostedPlugin/'), `Should resolve against the backend endpoint, was '${src}'`);
368
+ done();
369
+ }, 50);
370
+ });
371
+ it('should not render completion actions inside the step content', done => {
372
+ const selectedStep = createMockStep({ id: 's1', isComplete: false });
373
+ const walkthrough = createMockWalkthrough({ steps: [selectedStep] });
374
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { }, selectedStep: selectedStep, markdownRenderer: mockRenderer, onToggleStepDone: () => { }, onMarkAllStepsDone: () => { } }));
375
+ setTimeout(() => {
376
+ const content = container.querySelector('.gs-walkthrough-step-content');
377
+ // eslint-disable-next-line no-null/no-null
378
+ assert.strictEqual(content.querySelector('button'), null, 'The step content should carry no buttons');
379
+ // eslint-disable-next-line no-null/no-null
380
+ assert.strictEqual(content.querySelector('.gs-walkthrough-step-done'), null, 'The step content should carry no done indicator');
381
+ done();
382
+ }, 50);
383
+ });
384
+ it('should render markdown media as trusted and with an action handler, so that its links work', done => {
385
+ const originalFetch = global.fetch;
386
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
387
+ global.fetch = () => Promise.resolve({ ok: true, text: () => Promise.resolve('[Run](command:my.command)') });
388
+ const captured = [];
389
+ const recordingRenderer = {
390
+ render: (markdown, options) => {
391
+ captured.push({ ...markdown, options });
392
+ return { element: document.createElement('div'), dispose: () => { } };
393
+ }
394
+ };
395
+ const selectedStep = createMockStep({ id: 's1', media: { markdown: 'content.md' } });
396
+ const walkthrough = createMockWalkthrough({ steps: [selectedStep] });
397
+ root.render(React.createElement(walkthrough_detail_1.WalkthroughDetail, { logger: createMockLogger(), themeService: createMockThemeService(), walkthrough: walkthrough, onStepSelect: () => { }, onBack: () => { }, selectedStep: selectedStep, markdownRenderer: recordingRenderer, onLinkClick: () => { } }));
398
+ setTimeout(() => {
399
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
400
+ global.fetch = originalFetch;
401
+ const media = captured.find(entry => entry.value.includes('command:my.command'));
402
+ assert.ok(media, 'The media content should have been rendered');
403
+ assert.strictEqual(media?.isTrusted, true, 'Media has to be trusted, otherwise command links are stripped');
404
+ assert.ok(media?.options?.actionHandler, 'Media needs an action handler, otherwise its links are inert');
405
+ done();
406
+ }, 50);
407
+ });
408
+ });
409
+ //# sourceMappingURL=walkthrough-detail.spec.js.map