@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.
- package/lib/browser/getting-started-contribution.d.ts +30 -2
- package/lib/browser/getting-started-contribution.d.ts.map +1 -1
- package/lib/browser/getting-started-contribution.js +120 -0
- package/lib/browser/getting-started-contribution.js.map +1 -1
- package/lib/browser/getting-started-contribution.spec.d.ts +2 -0
- package/lib/browser/getting-started-contribution.spec.d.ts.map +1 -0
- package/lib/browser/getting-started-contribution.spec.js +152 -0
- package/lib/browser/getting-started-contribution.spec.js.map +1 -0
- package/lib/browser/getting-started-frontend-module.d.ts.map +1 -1
- package/lib/browser/getting-started-frontend-module.js +8 -0
- package/lib/browser/getting-started-frontend-module.js.map +1 -1
- package/lib/browser/getting-started-widget.d.ts +19 -0
- package/lib/browser/getting-started-widget.d.ts.map +1 -1
- package/lib/browser/getting-started-widget.js +57 -2
- package/lib/browser/getting-started-widget.js.map +1 -1
- package/lib/browser/getting-started-widget.spec.d.ts +2 -0
- package/lib/browser/getting-started-widget.spec.d.ts.map +1 -0
- package/lib/browser/getting-started-widget.spec.js +71 -0
- package/lib/browser/getting-started-widget.spec.js.map +1 -0
- package/lib/browser/walkthrough-card.d.ts +8 -0
- package/lib/browser/walkthrough-card.d.ts.map +1 -0
- package/lib/browser/walkthrough-card.js +40 -0
- package/lib/browser/walkthrough-card.js.map +1 -0
- package/lib/browser/walkthrough-card.spec.d.ts +2 -0
- package/lib/browser/walkthrough-card.spec.d.ts.map +1 -0
- package/lib/browser/walkthrough-card.spec.js +188 -0
- package/lib/browser/walkthrough-card.spec.js.map +1 -0
- package/lib/browser/walkthrough-detail.d.ts +19 -0
- package/lib/browser/walkthrough-detail.d.ts.map +1 -0
- package/lib/browser/walkthrough-detail.js +210 -0
- package/lib/browser/walkthrough-detail.js.map +1 -0
- package/lib/browser/walkthrough-detail.spec.d.ts +2 -0
- package/lib/browser/walkthrough-detail.spec.d.ts.map +1 -0
- package/lib/browser/walkthrough-detail.spec.js +409 -0
- package/lib/browser/walkthrough-detail.spec.js.map +1 -0
- package/lib/browser/walkthrough-icon.d.ts +10 -0
- package/lib/browser/walkthrough-icon.d.ts.map +1 -0
- package/lib/browser/walkthrough-icon.js +37 -0
- package/lib/browser/walkthrough-icon.js.map +1 -0
- package/lib/browser/walkthrough-section.d.ts +23 -0
- package/lib/browser/walkthrough-section.d.ts.map +1 -0
- package/lib/browser/walkthrough-section.js +65 -0
- package/lib/browser/walkthrough-section.js.map +1 -0
- package/lib/browser/walkthrough-section.spec.d.ts +2 -0
- package/lib/browser/walkthrough-section.spec.d.ts.map +1 -0
- package/lib/browser/walkthrough-section.spec.js +328 -0
- package/lib/browser/walkthrough-section.spec.js.map +1 -0
- package/lib/browser/walkthrough-service.d.ts +166 -0
- package/lib/browser/walkthrough-service.d.ts.map +1 -0
- package/lib/browser/walkthrough-service.js +505 -0
- package/lib/browser/walkthrough-service.js.map +1 -0
- package/lib/browser/walkthrough-service.spec.d.ts +2 -0
- package/lib/browser/walkthrough-service.spec.d.ts.map +1 -0
- package/lib/browser/walkthrough-service.spec.js +1026 -0
- package/lib/browser/walkthrough-service.spec.js.map +1 -0
- package/lib/common/getting-started-preferences.d.ts +1 -0
- package/lib/common/getting-started-preferences.d.ts.map +1 -1
- package/lib/common/getting-started-preferences.js +5 -0
- package/lib/common/getting-started-preferences.js.map +1 -1
- package/lib/common/walkthrough-commands.d.ts +11 -0
- package/lib/common/walkthrough-commands.d.ts.map +1 -0
- package/lib/common/walkthrough-commands.js +41 -0
- package/lib/common/walkthrough-commands.js.map +1 -0
- package/lib/common/walkthrough-types.d.ts +22 -0
- package/lib/common/walkthrough-types.d.ts.map +1 -0
- package/lib/common/walkthrough-types.js +18 -0
- package/lib/common/walkthrough-types.js.map +1 -0
- package/package.json +8 -7
- package/src/browser/getting-started-contribution.spec.ts +188 -0
- package/src/browser/getting-started-contribution.ts +139 -3
- package/src/browser/getting-started-frontend-module.ts +8 -0
- package/src/browser/getting-started-widget.spec.tsx +88 -0
- package/src/browser/getting-started-widget.tsx +70 -3
- package/src/browser/style/index.css +279 -0
- package/src/browser/walkthrough-card.spec.tsx +247 -0
- package/src/browser/walkthrough-card.tsx +62 -0
- package/src/browser/walkthrough-detail.spec.tsx +675 -0
- package/src/browser/walkthrough-detail.tsx +347 -0
- package/src/browser/walkthrough-icon.tsx +42 -0
- package/src/browser/walkthrough-section.spec.tsx +484 -0
- package/src/browser/walkthrough-section.tsx +119 -0
- package/src/browser/walkthrough-service.spec.ts +1279 -0
- package/src/browser/walkthrough-service.ts +565 -0
- package/src/common/getting-started-preferences.ts +6 -0
- package/src/common/walkthrough-commands.ts +37 -0
- package/src/common/walkthrough-types.ts +39 -0
|
@@ -142,3 +142,282 @@ body {
|
|
|
142
142
|
box-shadow: 0 0 15px rgba(0, 0, 0, 0.4);
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
|
+
|
|
146
|
+
/* Walkthrough styles */
|
|
147
|
+
|
|
148
|
+
/* The cards form a quiet single column: card-like blocks, but without borders. */
|
|
149
|
+
.gs-walkthrough-cards {
|
|
150
|
+
display: flex;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
gap: var(--theia-ui-padding);
|
|
153
|
+
margin-top: var(--theia-ui-padding);
|
|
154
|
+
max-width: 640px;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.gs-walkthrough-card {
|
|
158
|
+
padding: calc(var(--theia-ui-padding) * 1.5) calc(var(--theia-ui-padding) * 2);
|
|
159
|
+
border-radius: calc(var(--theia-ui-padding) / 2);
|
|
160
|
+
cursor: pointer;
|
|
161
|
+
background-color: var(--theia-walkthrough-card-background);
|
|
162
|
+
/* Only set in high contrast themes, where the card has no background to delimit it. */
|
|
163
|
+
border: var(--theia-border-width) solid var(--theia-contrastBorder);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/*
|
|
167
|
+
* High contrast themes define none of the `welcomePage`, `list` and `secondaryButton` hover colors, which
|
|
168
|
+
* leaves the hover feedback below inert. `contrastActiveBorder` is set in those themes only and is the
|
|
169
|
+
* established affordance for it, see `dockpanel.css` in `@theia/core`.
|
|
170
|
+
*/
|
|
171
|
+
.gs-walkthrough-card:hover {
|
|
172
|
+
background-color: var(--theia-welcomePage-buttonHoverBackground);
|
|
173
|
+
outline: var(--theia-border-width) dashed var(--theia-contrastActiveBorder);
|
|
174
|
+
outline-offset: calc(var(--theia-border-width) * -1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.gs-walkthrough-card:focus {
|
|
178
|
+
outline: var(--theia-border-width) solid var(--theia-focusBorder);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.gs-walkthrough-card-header {
|
|
182
|
+
display: flex;
|
|
183
|
+
align-items: center;
|
|
184
|
+
gap: calc(var(--theia-ui-padding) / 2);
|
|
185
|
+
margin-bottom: calc(var(--theia-ui-padding) / 2);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.gs-walkthrough-card-title {
|
|
189
|
+
font-size: var(--theia-ui-font-size1);
|
|
190
|
+
font-weight: 600;
|
|
191
|
+
margin: 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.gs-walkthrough-icon {
|
|
195
|
+
font-size: var(--theia-ui-font-size1);
|
|
196
|
+
flex: 0 0 auto;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* The extension icon is an image, so it needs an explicit box to sit in. */
|
|
200
|
+
.gs-walkthrough-plugin-icon {
|
|
201
|
+
width: var(--theia-icon-size);
|
|
202
|
+
height: var(--theia-icon-size);
|
|
203
|
+
object-fit: contain;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* Kept to a single line so that every card stays the same height. */
|
|
207
|
+
.gs-walkthrough-card-description {
|
|
208
|
+
color: var(--theia-descriptionForeground);
|
|
209
|
+
font-size: var(--theia-ui-font-size0);
|
|
210
|
+
margin: 0;
|
|
211
|
+
overflow: hidden;
|
|
212
|
+
text-overflow: ellipsis;
|
|
213
|
+
white-space: nowrap;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.gs-walkthrough-card-progress {
|
|
217
|
+
display: flex;
|
|
218
|
+
align-items: center;
|
|
219
|
+
gap: var(--theia-ui-padding);
|
|
220
|
+
margin-top: var(--theia-ui-padding);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.gs-walkthrough-progress-bar {
|
|
224
|
+
flex: 0 1 180px;
|
|
225
|
+
height: calc(var(--theia-ui-padding) / 3);
|
|
226
|
+
background-color: var(--theia-walkthrough-progress-background);
|
|
227
|
+
border-radius: calc(var(--theia-ui-padding) / 3);
|
|
228
|
+
overflow: hidden;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.gs-walkthrough-progress-fill {
|
|
232
|
+
height: 100%;
|
|
233
|
+
/* The fallback keeps the filled part visible should a theme leave `progressBar.background` unset. */
|
|
234
|
+
background-color: var(--theia-progressBar-background, var(--theia-focusBorder));
|
|
235
|
+
border-radius: calc(var(--theia-ui-padding) / 3);
|
|
236
|
+
transition: width 0.3s ease;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.gs-walkthrough-progress-text {
|
|
240
|
+
color: var(--theia-walkthrough-progress-foreground);
|
|
241
|
+
font-size: var(--theia-ui-font-size0);
|
|
242
|
+
white-space: nowrap;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.gs-walkthrough-more {
|
|
246
|
+
display: inline-block;
|
|
247
|
+
margin-top: calc(var(--theia-ui-padding) / 2);
|
|
248
|
+
padding-left: var(--theia-ui-padding);
|
|
249
|
+
cursor: pointer;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* A selected walkthrough takes over the whole welcome view. */
|
|
253
|
+
.gs-walkthrough-container {
|
|
254
|
+
padding: calc(var(--theia-ui-padding) * 6);
|
|
255
|
+
overflow: auto;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.gs-walkthrough-detail {
|
|
259
|
+
display: flex;
|
|
260
|
+
flex-direction: column;
|
|
261
|
+
height: 100%;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.gs-walkthrough-detail-header {
|
|
265
|
+
margin-bottom: calc(var(--theia-ui-padding) * 4);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.gs-walkthrough-detail-body {
|
|
269
|
+
display: flex;
|
|
270
|
+
flex: 1;
|
|
271
|
+
flex-wrap: wrap;
|
|
272
|
+
align-items: flex-start;
|
|
273
|
+
gap: calc(var(--theia-ui-padding) * 4);
|
|
274
|
+
min-height: 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.gs-walkthrough-back-link {
|
|
278
|
+
cursor: pointer;
|
|
279
|
+
display: inline-flex;
|
|
280
|
+
align-items: center;
|
|
281
|
+
gap: calc(var(--theia-ui-padding) / 2);
|
|
282
|
+
margin-bottom: calc(var(--theia-ui-padding) * 6);
|
|
283
|
+
font-weight: 500;
|
|
284
|
+
text-decoration: none;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/*
|
|
288
|
+
* The label carries the underline rather than the link itself: text decoration of a flex container is not
|
|
289
|
+
* propagated to its flex items, and underlining the arrow icon along with the label would look off anyway.
|
|
290
|
+
*/
|
|
291
|
+
.gs-walkthrough-back-link:hover .gs-walkthrough-back-label {
|
|
292
|
+
text-decoration: underline;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.gs-walkthrough-detail-title {
|
|
296
|
+
display: flex;
|
|
297
|
+
align-items: center;
|
|
298
|
+
gap: var(--theia-ui-padding);
|
|
299
|
+
font-size: var(--theia-ui-font-size3);
|
|
300
|
+
font-weight: 600;
|
|
301
|
+
margin: 0;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.gs-walkthrough-detail-title .gs-walkthrough-plugin-icon {
|
|
305
|
+
width: var(--theia-ui-font-size3);
|
|
306
|
+
height: var(--theia-ui-font-size3);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.gs-walkthrough-detail-description {
|
|
310
|
+
color: var(--theia-descriptionForeground);
|
|
311
|
+
margin: var(--theia-ui-padding) 0 0 0;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.gs-walkthrough-steps {
|
|
315
|
+
display: flex;
|
|
316
|
+
flex: 1 1 280px;
|
|
317
|
+
flex-direction: column;
|
|
318
|
+
gap: calc(var(--theia-ui-padding) / 2);
|
|
319
|
+
max-width: 420px;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.gs-walkthrough-step-item {
|
|
323
|
+
display: flex;
|
|
324
|
+
align-items: center;
|
|
325
|
+
gap: var(--theia-ui-padding);
|
|
326
|
+
padding: var(--theia-ui-padding);
|
|
327
|
+
border-radius: calc(var(--theia-ui-padding) / 2);
|
|
328
|
+
border-left: calc(var(--theia-border-width) * 2) solid transparent;
|
|
329
|
+
cursor: pointer;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.gs-walkthrough-step-item:hover {
|
|
333
|
+
background-color: var(--theia-list-hoverBackground);
|
|
334
|
+
outline: var(--theia-border-width) dashed var(--theia-contrastActiveBorder);
|
|
335
|
+
outline-offset: calc(var(--theia-border-width) * -1);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/*
|
|
339
|
+
* The selected step keeps the regular foreground colors: the active selection background does not
|
|
340
|
+
* guarantee readable contrast for the step title in every theme, so the selection is conveyed by a
|
|
341
|
+
* quiet background plus an accent border instead.
|
|
342
|
+
*/
|
|
343
|
+
.gs-walkthrough-step-item.selected {
|
|
344
|
+
background-color: var(--theia-list-inactiveSelectionBackground);
|
|
345
|
+
border-left-color: var(--theia-focusBorder);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.gs-walkthrough-step-item.completed .gs-walkthrough-step-icon {
|
|
349
|
+
color: var(--theia-walkthrough-stepCompleted-foreground);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.gs-walkthrough-step-item .gs-walkthrough-step-icon:hover {
|
|
353
|
+
color: var(--theia-focusBorder);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.gs-walkthrough-step-icon {
|
|
357
|
+
font-size: var(--theia-ui-font-size2);
|
|
358
|
+
cursor: pointer;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.gs-walkthrough-step-title {
|
|
362
|
+
color: var(--theia-walkthrough-stepTitle-foreground);
|
|
363
|
+
font-weight: 500;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.gs-walkthrough-step-content {
|
|
367
|
+
flex: 2 1 360px;
|
|
368
|
+
min-width: 0;
|
|
369
|
+
padding: calc(var(--theia-ui-padding) * 2);
|
|
370
|
+
border: var(--theia-border-width) solid var(--theia-panel-border);
|
|
371
|
+
border-radius: var(--theia-ui-padding);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.gs-walkthrough-step-detail h3 {
|
|
375
|
+
margin-top: 0;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.gs-walkthrough-step-detail p {
|
|
379
|
+
color: var(--theia-descriptionForeground);
|
|
380
|
+
line-height: var(--theia-content-line-height);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.gs-walkthrough-mark-all-done {
|
|
384
|
+
align-self: flex-start;
|
|
385
|
+
margin: var(--theia-ui-padding) 0 0 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/* `.theia-button` brings no hover state of its own. */
|
|
389
|
+
.gs-walkthrough-mark-all-done:hover:not([disabled]) {
|
|
390
|
+
background-color: var(--theia-secondaryButton-hoverBackground);
|
|
391
|
+
outline: var(--theia-border-width) dashed var(--theia-contrastActiveBorder);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.gs-walkthrough-media-image {
|
|
395
|
+
max-width: 100%;
|
|
396
|
+
border-radius: calc(var(--theia-ui-padding) / 2);
|
|
397
|
+
margin-top: var(--theia-ui-padding);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.gs-walkthrough-media-markdown {
|
|
401
|
+
margin-top: var(--theia-ui-padding);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.gs-walkthrough-step-description {
|
|
405
|
+
line-height: var(--theia-content-line-height);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.gs-walkthrough-step-description a {
|
|
409
|
+
color: var(--theia-textLink-foreground);
|
|
410
|
+
text-decoration: none;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.gs-walkthrough-step-description a:hover {
|
|
414
|
+
text-decoration: underline;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.gs-walkthrough-step-description code {
|
|
418
|
+
font-family: var(--theia-code-font-family);
|
|
419
|
+
font-size: var(--theia-code-font-size);
|
|
420
|
+
background: var(--theia-textCodeBlock-background);
|
|
421
|
+
padding: 0 calc(var(--theia-ui-padding) / 2);
|
|
422
|
+
border-radius: calc(var(--theia-ui-padding) / 3);
|
|
423
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import * as assert from 'assert';
|
|
18
|
+
import * as React from 'react';
|
|
19
|
+
import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom';
|
|
20
|
+
|
|
21
|
+
let disableJSDOM = enableJSDOM();
|
|
22
|
+
|
|
23
|
+
import { createRoot, Root } from 'react-dom/client';
|
|
24
|
+
import { Walkthrough, WalkthroughStep } from '../common/walkthrough-types';
|
|
25
|
+
import { WalkthroughCard } from './walkthrough-card';
|
|
26
|
+
|
|
27
|
+
function createMockStep(overrides?: Partial<WalkthroughStep>): WalkthroughStep {
|
|
28
|
+
return {
|
|
29
|
+
id: 'step-1',
|
|
30
|
+
title: 'Test Step',
|
|
31
|
+
description: 'A test step description',
|
|
32
|
+
isComplete: false,
|
|
33
|
+
...overrides
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createMockWalkthrough(overrides?: Partial<Walkthrough>): Walkthrough {
|
|
38
|
+
return {
|
|
39
|
+
id: 'test-walkthrough',
|
|
40
|
+
title: 'Test Walkthrough',
|
|
41
|
+
description: 'A test walkthrough description',
|
|
42
|
+
steps: [
|
|
43
|
+
createMockStep({ id: 'step-1', title: 'First Step' }),
|
|
44
|
+
createMockStep({ id: 'step-2', title: 'Second Step' }),
|
|
45
|
+
createMockStep({ id: 'step-3', title: 'Third Step' })
|
|
46
|
+
],
|
|
47
|
+
pluginId: 'test.plugin',
|
|
48
|
+
...overrides
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('WalkthroughCard', () => {
|
|
53
|
+
let container: HTMLElement;
|
|
54
|
+
let root: Root;
|
|
55
|
+
|
|
56
|
+
before(() => disableJSDOM = enableJSDOM());
|
|
57
|
+
after(() => disableJSDOM());
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
container = document.createElement('div');
|
|
61
|
+
document.body.appendChild(container);
|
|
62
|
+
root = createRoot(container);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
root.unmount();
|
|
67
|
+
document.body.removeChild(container);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should render walkthrough title and description', done => {
|
|
71
|
+
const walkthrough = createMockWalkthrough({
|
|
72
|
+
title: 'My Walkthrough',
|
|
73
|
+
description: 'My description'
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
root.render(
|
|
77
|
+
<WalkthroughCard walkthrough={walkthrough} onSelect={() => { }} />
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
const title = container.querySelector('.gs-walkthrough-card-title');
|
|
82
|
+
assert.ok(title, 'Title element should exist');
|
|
83
|
+
assert.ok(title?.textContent?.includes('My Walkthrough'), 'Should contain the walkthrough title');
|
|
84
|
+
|
|
85
|
+
const description = container.querySelector('.gs-walkthrough-card-description');
|
|
86
|
+
assert.ok(description, 'Description element should exist');
|
|
87
|
+
assert.ok(description?.textContent?.includes('My description'), 'Should contain the walkthrough description');
|
|
88
|
+
done();
|
|
89
|
+
}, 50);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should display progress bar with correct percentage', done => {
|
|
93
|
+
const walkthrough = createMockWalkthrough({
|
|
94
|
+
steps: [
|
|
95
|
+
createMockStep({ id: 's1', isComplete: true }),
|
|
96
|
+
createMockStep({ id: 's2', isComplete: false }),
|
|
97
|
+
createMockStep({ id: 's3', isComplete: false })
|
|
98
|
+
]
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
root.render(
|
|
102
|
+
<WalkthroughCard walkthrough={walkthrough} onSelect={() => { }} />
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
setTimeout(() => {
|
|
106
|
+
const progressFill = container.querySelector('.gs-walkthrough-progress-fill') as HTMLElement;
|
|
107
|
+
assert.ok(progressFill, 'Progress fill element should exist');
|
|
108
|
+
const expectedWidth = `${(1 / 3) * 100}%`;
|
|
109
|
+
assert.strictEqual(progressFill.style.width, expectedWidth, 'Progress fill should reflect 1 of 3 steps');
|
|
110
|
+
|
|
111
|
+
const progressText = container.querySelector('.gs-walkthrough-progress-text');
|
|
112
|
+
assert.ok(progressText, 'Progress text should exist');
|
|
113
|
+
assert.ok(progressText?.textContent?.includes('1'), 'Should show completed count');
|
|
114
|
+
assert.ok(progressText?.textContent?.includes('3'), 'Should show total count');
|
|
115
|
+
done();
|
|
116
|
+
}, 50);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should call onSelect when card is clicked', done => {
|
|
120
|
+
const walkthrough = createMockWalkthrough();
|
|
121
|
+
let selectedWalkthrough: Walkthrough | undefined;
|
|
122
|
+
|
|
123
|
+
root.render(
|
|
124
|
+
<WalkthroughCard walkthrough={walkthrough} onSelect={w => { selectedWalkthrough = w; }} />
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
setTimeout(() => {
|
|
128
|
+
const card = container.querySelector('.gs-walkthrough-card') as HTMLElement;
|
|
129
|
+
assert.ok(card, 'Card element should exist');
|
|
130
|
+
card.click();
|
|
131
|
+
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
assert.ok(selectedWalkthrough, 'onSelect should have been called');
|
|
134
|
+
assert.strictEqual(selectedWalkthrough?.id, walkthrough.id, 'Should pass the walkthrough to onSelect');
|
|
135
|
+
done();
|
|
136
|
+
}, 50);
|
|
137
|
+
}, 50);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should render icon when walkthrough.icon is provided', done => {
|
|
141
|
+
const walkthrough = createMockWalkthrough({ icon: 'play' });
|
|
142
|
+
|
|
143
|
+
root.render(
|
|
144
|
+
<WalkthroughCard walkthrough={walkthrough} onSelect={() => { }} />
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
const icon = container.querySelector('.gs-walkthrough-icon');
|
|
149
|
+
assert.ok(icon, 'Icon element should exist');
|
|
150
|
+
assert.ok(icon?.classList.contains('codicon-play'), 'Should have the codicon-play class');
|
|
151
|
+
done();
|
|
152
|
+
}, 50);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should not render icon when walkthrough.icon is not provided', done => {
|
|
156
|
+
const walkthrough = createMockWalkthrough({ icon: undefined });
|
|
157
|
+
|
|
158
|
+
root.render(
|
|
159
|
+
<WalkthroughCard walkthrough={walkthrough} onSelect={() => { }} />
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
setTimeout(() => {
|
|
163
|
+
const icon = container.querySelector('.gs-walkthrough-icon');
|
|
164
|
+
// eslint-disable-next-line no-null/no-null
|
|
165
|
+
assert.strictEqual(icon, null, 'Icon element should not exist');
|
|
166
|
+
done();
|
|
167
|
+
}, 50);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should show 0% progress when no steps are complete', done => {
|
|
171
|
+
const walkthrough = createMockWalkthrough();
|
|
172
|
+
|
|
173
|
+
root.render(
|
|
174
|
+
<WalkthroughCard walkthrough={walkthrough} onSelect={() => { }} />
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
const progressFill = container.querySelector('.gs-walkthrough-progress-fill') as HTMLElement;
|
|
179
|
+
assert.ok(progressFill, 'Progress fill element should exist');
|
|
180
|
+
assert.strictEqual(progressFill.style.width, '0%', 'Progress should be 0%');
|
|
181
|
+
done();
|
|
182
|
+
}, 50);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should show 100% progress when all steps are complete', done => {
|
|
186
|
+
const walkthrough = createMockWalkthrough({
|
|
187
|
+
steps: [
|
|
188
|
+
createMockStep({ id: 's1', isComplete: true }),
|
|
189
|
+
createMockStep({ id: 's2', isComplete: true }),
|
|
190
|
+
]
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
root.render(
|
|
194
|
+
<WalkthroughCard walkthrough={walkthrough} onSelect={() => { }} />
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
setTimeout(() => {
|
|
198
|
+
const progressFill = container.querySelector('.gs-walkthrough-progress-fill') as HTMLElement;
|
|
199
|
+
assert.ok(progressFill, 'Progress fill element should exist');
|
|
200
|
+
assert.strictEqual(progressFill.style.width, '100%', 'Progress should be 100%');
|
|
201
|
+
done();
|
|
202
|
+
}, 50);
|
|
203
|
+
});
|
|
204
|
+
it('should fall back to the extension icon when the walkthrough contributes none', done => {
|
|
205
|
+
const walkthrough = createMockWalkthrough({ icon: undefined, pluginIcon: 'http://localhost/icon.png' });
|
|
206
|
+
|
|
207
|
+
root.render(
|
|
208
|
+
<WalkthroughCard walkthrough={walkthrough} onSelect={() => { }} />
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
setTimeout(() => {
|
|
212
|
+
const icon = container.querySelector('.gs-walkthrough-plugin-icon') as HTMLImageElement;
|
|
213
|
+
assert.ok(icon, 'The extension icon should be rendered');
|
|
214
|
+
assert.strictEqual(icon.getAttribute('src'), 'http://localhost/icon.png', 'It should use the resolved icon URL');
|
|
215
|
+
done();
|
|
216
|
+
}, 50);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should prefer a contributed codicon over the extension icon', done => {
|
|
220
|
+
const walkthrough = createMockWalkthrough({ icon: 'play', pluginIcon: 'http://localhost/icon.png' });
|
|
221
|
+
|
|
222
|
+
root.render(
|
|
223
|
+
<WalkthroughCard walkthrough={walkthrough} onSelect={() => { }} />
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
setTimeout(() => {
|
|
227
|
+
assert.ok(container.querySelector('.codicon-play'), 'The contributed codicon should win');
|
|
228
|
+
// eslint-disable-next-line no-null/no-null
|
|
229
|
+
assert.strictEqual(container.querySelector('.gs-walkthrough-plugin-icon'), null, 'The extension icon should be skipped');
|
|
230
|
+
done();
|
|
231
|
+
}, 50);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should render no icon when neither is available', done => {
|
|
235
|
+
const walkthrough = createMockWalkthrough({ icon: undefined, pluginIcon: undefined });
|
|
236
|
+
|
|
237
|
+
root.render(
|
|
238
|
+
<WalkthroughCard walkthrough={walkthrough} onSelect={() => { }} />
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
setTimeout(() => {
|
|
242
|
+
// eslint-disable-next-line no-null/no-null
|
|
243
|
+
assert.strictEqual(container.querySelector('.gs-walkthrough-icon'), null, 'No icon element should exist');
|
|
244
|
+
done();
|
|
245
|
+
}, 50);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { nls } from '@theia/core/lib/common/nls';
|
|
18
|
+
import * as React from '@theia/core/shared/react';
|
|
19
|
+
import { Walkthrough } from '../common/walkthrough-types';
|
|
20
|
+
import { WalkthroughIcon } from './walkthrough-icon';
|
|
21
|
+
|
|
22
|
+
export interface WalkthroughCardProps {
|
|
23
|
+
walkthrough: Walkthrough;
|
|
24
|
+
onSelect: (walkthrough: Walkthrough) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function WalkthroughCard(props: WalkthroughCardProps): React.ReactElement {
|
|
28
|
+
const { walkthrough, onSelect } = props;
|
|
29
|
+
const completedSteps = walkthrough.steps.filter(s => s.isComplete).length;
|
|
30
|
+
const totalSteps = walkthrough.steps.length;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
className='gs-walkthrough-card'
|
|
35
|
+
role='button'
|
|
36
|
+
tabIndex={0}
|
|
37
|
+
onClick={() => onSelect(walkthrough)}
|
|
38
|
+
onKeyDown={(e: React.KeyboardEvent) => {
|
|
39
|
+
if (e.key === 'Enter') {
|
|
40
|
+
onSelect(walkthrough);
|
|
41
|
+
}
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
<div className='gs-walkthrough-card-header'>
|
|
45
|
+
<WalkthroughIcon walkthrough={walkthrough} />
|
|
46
|
+
<h3 className='gs-walkthrough-card-title'>{walkthrough.title}</h3>
|
|
47
|
+
</div>
|
|
48
|
+
<p className='gs-walkthrough-card-description'>{walkthrough.description}</p>
|
|
49
|
+
<div className='gs-walkthrough-card-progress'>
|
|
50
|
+
<div className='gs-walkthrough-progress-bar'>
|
|
51
|
+
<div
|
|
52
|
+
className='gs-walkthrough-progress-fill'
|
|
53
|
+
style={{ width: totalSteps > 0 ? `${(completedSteps / totalSteps) * 100}%` : '0%' }}
|
|
54
|
+
></div>
|
|
55
|
+
</div>
|
|
56
|
+
<span className='gs-walkthrough-progress-text'>
|
|
57
|
+
{nls.localizeByDefault('{0} of {1}', String(completedSteps), String(totalSteps))}
|
|
58
|
+
</span>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|