• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MorganKryze / ConsoleAppVisuals / 14270412817

04 Apr 2025 05:01PM UTC coverage: 96.208% (-0.05%) from 96.257%
14270412817

push

github

MorganKryze
🚑 fix: (Dialog) tests not taking in account latest update on dialog

945 of 1055 branches covered (89.57%)

Branch coverage included in aggregate %.

2125 of 2136 relevant lines covered (99.49%)

251.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.89
/src/ConsoleAppVisuals/elements/interactive_elements/Dialog.cs
1
/*
2
    Copyright (c) 2024 Yann M. Vidamment (MorganKryze)
3
    Licensed under GNU GPL v2.0. See full license at: https://github.com/MorganKryze/ConsoleAppVisuals/blob/main/LICENSE.md
4
*/
5
namespace ConsoleAppVisuals.InteractiveElements;
6

7
/// <summary>
8
/// The <see cref="Dialog"/> is an interactive element that displays a dialog bow with one or two choices.
9
/// See <see cref="DialogOption"/> enum for the possible outputs of a dialog.
10
/// </summary>
11
/// <remarks>
12
/// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
13
/// </remarks>
14
/// <value><see cref="Dialog"/> response type is: <see cref="DialogOption"/></value>
15
public class Dialog : InteractiveElement<DialogOption>
16
{
17
    #region Constants
18
    const int EMBED_MARGIN = 2;
19

20
    /// <summary>
21
    /// The width ratio between the two options of the Dialog.
22
    /// It ensures that the options are not too close to each other.
23
    /// </summary>
24
    const double WIDTH_RATIO = 1.2;
25
    #endregion
26

27
    #region Fields
28
    private List<string> _lines;
29
    private string? _leftOption;
30
    private string? _rightOption;
31
    private TextAlignment _align;
32
    private Placement _placement;
33
    private readonly Borders _borders;
34
    private List<string>? _textToDisplay;
35
    #endregion
36

37
    #region Default Properties
38
    /// <summary>
39
    /// Gets the position of the Dialog.
40
    /// </summary>
41
    public override Placement Placement => _placement;
4✔
42

43
    /// <summary>
44
    /// Gets the alignment of the Dialog.
45
    /// </summary>
46
    public override TextAlignment TextAlignment => _align;
4✔
47

48
    /// <summary>
49
    /// Gets the height of the Dialog.
50
    /// </summary>
51
    public override int Height => _textToDisplay!.Count;
1✔
52

53
    /// <summary>
54
    /// Gets the width of the Dialog.
55
    /// </summary>
56
    public override int Width => _textToDisplay!.Max(s => s.Length);
7✔
57
    #endregion
58

59
    #region Properties
60
    /// <summary>
61
    /// Gets the rows of the Dialog.
62
    /// </summary>
63
    public List<string> Lines => _lines;
7✔
64

65
    /// <summary>
66
    /// Gets the text of the left option.
67
    /// </summary>
68
    public string? LeftOption => _leftOption;
4✔
69

70
    /// <summary>
71
    /// Gets the text of the right option.
72
    /// </summary>
73
    public string? RightOption => _rightOption;
4✔
74

75
    /// <summary>
76
    /// Gets the borders of the Dialog.
77
    /// </summary>
78
    public Borders Borders => _borders;
680✔
79

80
    /// <summary>
81
    /// Gets the border type of the selector.
82
    /// </summary>
83
    public BordersType BordersType => _borders.Type;
3✔
84

85
    /// <summary>
86
    /// Gets the text to display.
87
    /// </summary>
88
    public List<string>? TextToDisplay => _textToDisplay;
2✔
89

90
    private int MaxLineLength => Math.Max(_lines.Max(s => s.Length), CalculateMinWidthForOptions());
1,108✔
91

92
    private int CalculateMinWidthForOptions()
93
    {
94
        int DIALOG_MARGIN = 4;
343✔
95
        if (_leftOption == null && _rightOption == null)
343!
96
            return 0;
×
97

98
        if (_leftOption == null)
343✔
99
            return _rightOption!.Length;
24✔
100
        if (_rightOption == null)
319✔
101
            return _leftOption.Length;
22✔
102

103
        return _leftOption.Length
297✔
104
            + _rightOption.Length
297✔
105
            + DIALOG_MARGIN
297✔
106
            + (int)(_leftOption.Length * (WIDTH_RATIO - 1));
297✔
107
    }
108
    #endregion
109

110
    #region Constructor
111
    /// <summary>
112
    /// The <see cref="Dialog"/> is an interactive element that displays a dialog bow with one or two choices.
113
    /// See <see cref="DialogOption"/> enum for the possible outputs of a dialog.
114
    /// </summary>
115
    /// <param name="lines">The text to display.</param>
116
    /// <param name="leftOption">The text of the left option. Null for no button.</param>
117
    /// <param name="rightOption">The text of the right option. Null for no button.</param>
118
    /// <param name="align">The alignment of the Dialog.</param>
119
    /// <param name="placement">The placement of the Dialog element.</param>
120
    /// <param name="bordersType">The type of border to display.</param>
121
    /// <remarks>
122
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
123
    /// </remarks>
124
    /// <value><see cref="Dialog"/> response type is: <see cref="DialogOption"/></value>
125
    public Dialog(
36✔
126
        List<string> lines,
36✔
127
        string? leftOption = null,
36✔
128
        string? rightOption = null,
36✔
129
        TextAlignment align = TextAlignment.Left,
36✔
130
        Placement placement = Placement.TopCenter,
36✔
131
        BordersType bordersType = BordersType.SingleStraight
36✔
132
    )
36✔
133
    {
134
        _lines = lines;
36✔
135
        _leftOption = leftOption;
36✔
136
        _rightOption = rightOption;
36✔
137
        _align = align;
36✔
138
        _placement = placement;
36✔
139
        _borders = new Borders(bordersType);
36✔
140
        if (_lines.Count != 0)
36✔
141
        {
142
            Build();
34✔
143
        }
144
    }
36✔
145
    #endregion
146

147
    #region Update Methods
148
    /// <summary>
149
    /// Updates the text of the first option.
150
    /// </summary>
151
    /// <param name="text">The new text of the first option.</param>
152
    /// <remarks>
153
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
154
    /// </remarks>
155
    public void UpdateRightOption(string? text)
156
    {
157
        _rightOption = text;
2✔
158
        Build();
2✔
159
    }
2✔
160

161
    /// <summary>
162
    /// Updates the text of the second option.
163
    /// </summary>
164
    /// <param name="text">The new text of the second option.</param>
165
    /// <remarks>
166
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
167
    /// </remarks>
168
    public void UpdateLeftOption(string? text)
169
    {
170
        _leftOption = text;
2✔
171
        Build();
2✔
172
    }
2✔
173

174
    /// <summary>
175
    /// Updates the text of the Dialog.
176
    /// </summary>
177
    /// <param name="lines">The new text of the Dialog.</param>
178
    /// <remarks>
179
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
180
    /// </remarks>
181
    public void UpdateLines(List<string> lines)
182
    {
183
        _lines.Clear();
2✔
184
        _lines = lines;
2✔
185
        Build();
2✔
186
    }
1✔
187

188
    /// <summary>
189
    /// Updates the placement of the Dialog.
190
    /// </summary>
191
    /// <param name="placement">The new placement of the Dialog.</param>
192
    /// <remarks>
193
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
194
    /// </remarks>
195
    public void UpdatePlacement(Placement placement)
196
    {
197
        _placement = placement;
2✔
198
    }
2✔
199

200
    /// <summary>
201
    /// Updates the alignment of the Dialog.
202
    /// </summary>
203
    /// <param name="align">The new alignment of the Dialog.</param>
204
    /// <remarks>
205
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
206
    /// </remarks>
207
    public void UpdateTextAlignment(TextAlignment align)
208
    {
209
        _align = align;
2✔
210
        Build();
2✔
211
    }
2✔
212

213
    /// <summary>
214
    /// Updates the borders of the Dialog.
215
    /// </summary>
216
    /// <param name="bordersType">The new border type of the Dialog.</param>
217
    /// <remarks>
218
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
219
    /// </remarks>
220
    public void UpdateBordersType(BordersType bordersType)
221
    {
222
        _borders.UpdateBordersType(bordersType);
2✔
223
        Build();
2✔
224
    }
2✔
225
    #endregion
226

227
    #region Manipulation Methods
228
    /// <summary>
229
    /// Adds a line to the Dialog.
230
    /// </summary>
231
    /// <param name="line">The line to add.</param>
232
    /// <remarks>
233
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
234
    /// </remarks>
235
    public void AddLine(string line)
236
    {
237
        _lines.Add(line);
1✔
238
        Build();
1✔
239
    }
1✔
240

241
    /// <summary>
242
    /// Inserts a line to the Dialog.
243
    /// </summary>
244
    /// <param name="index">The index where to insert the line.</param>
245
    /// <param name="line">The line to insert.</param>
246
    /// <remarks>
247
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
248
    /// </remarks>
249
    public void InsertLine(int index, string line)
250
    {
251
        if (index < 0 || index >= _lines.Count)
3✔
252
        {
253
            throw new ArgumentOutOfRangeException(nameof(index), "The index is out of range.");
2✔
254
        }
255
        _lines.Insert(index, line);
1✔
256
        Build();
1✔
257
    }
1✔
258

259
    /// <summary>
260
    /// Removes a line from the Dialog.
261
    /// </summary>
262
    /// <param name="line">The line to remove.</param>
263
    /// <remarks>
264
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
265
    /// </remarks>
266
    public void RemoveLine(string line)
267
    {
268
        if (!_lines.Contains(line))
2✔
269
        {
270
            throw new ArgumentException("The line is not in the text.");
1✔
271
        }
272
        _lines.Remove(line);
1✔
273
        Build();
1✔
274
    }
1✔
275

276
    /// <summary>
277
    /// Removes a line from the Dialog.
278
    /// </summary>
279
    /// <param name="index">The index of the line to remove.</param>
280
    /// <remarks>
281
    /// For more information, consider visiting the documentation available <a href="https://morgankryze.github.io/ConsoleAppVisuals/">here</a>.
282
    /// </remarks>
283
    public void RemoveLine(int index)
284
    {
285
        if (index < 0 || index >= _lines.Count)
3✔
286
        {
287
            throw new ArgumentOutOfRangeException(nameof(index), "The index is out of range.");
2✔
288
        }
289
        _lines.RemoveAt(index);
1✔
290
        Build();
1✔
291
    }
1✔
292
    #endregion
293

294
    #region Rendering
295
    [Visual]
296
    private void Build()
297
    {
298
        if (_lines.Count == 0)
299
        {
300
            throw new InvalidOperationException(
301
                "The lines are empty. You must provide at least one line to display in the Dialog"
302
            );
303
        }
304

305
        _textToDisplay = new List<string>();
306

307
        _textToDisplay.Add(
308
            Borders.TopLeft
309
                + new string(Borders.Horizontal, MaxLineLength + EMBED_MARGIN)
310
                + Borders.TopRight
311
        );
312

313
        foreach (var line in _lines)
314
        {
315
            var lineToDisplay = $"{Borders.Vertical} ";
316
            switch (_align)
317
            {
318
                case TextAlignment.Center:
319
                    int totalPadding = MaxLineLength - line.Length;
320
                    int padLeft = totalPadding / 2;
321
                    lineToDisplay += line.PadLeft(line.Length + padLeft).PadRight(MaxLineLength);
322
                    break;
323
                case TextAlignment.Left:
324
                    lineToDisplay += line.PadRight(MaxLineLength);
325
                    break;
326
                case TextAlignment.Right:
327
                    lineToDisplay += line.PadLeft(MaxLineLength);
328
                    break;
329
            }
330
            lineToDisplay += $" {Borders.Vertical}";
331
            _textToDisplay.Add(lineToDisplay);
332
        }
333

334
        if (_rightOption is not null || _leftOption is not null)
335
        {
336
            AddOptions();
337
        }
338

339
        _textToDisplay.Add(
340
            Borders.BottomLeft
341
                + new string(Borders.Horizontal, MaxLineLength + EMBED_MARGIN)
342
                + Borders.BottomRight
343
        );
344

345
        [Visual]
346
        void AddOptions()
347
        {
348
            _textToDisplay!.Add(
349
                $"{Borders.Vertical} " + new string(' ', MaxLineLength) + $" {Borders.Vertical}"
350
            );
351

352
            string optionLine =
353
                $"{Borders.Vertical} " + new string(' ', MaxLineLength) + $" {Borders.Vertical}";
354

355
            if (_leftOption is not null)
356
            {
357
                optionLine = optionLine.Remove(2, _leftOption.Length);
358
                optionLine = optionLine.Insert(2, _leftOption);
359
            }
360

361
            if (_rightOption is not null)
362
            {
363
                int insertPosition = optionLine.Length - _rightOption.Length - 2;
364
                optionLine = optionLine.Remove(insertPosition, _rightOption.Length);
365
                optionLine = optionLine.Insert(insertPosition, _rightOption);
366
            }
367

368
            _textToDisplay.Add(optionLine);
369
        }
370
    }
371

372
    /// <summary>
373
    /// Renders the Dialog.
374
    /// </summary>
375
    [Visual]
376
    protected override void RenderElementActions()
377
    {
378
        Build();
379

380
        DialogOption optionSelectedIndex = SetDefaultValue();
381

382
        bool loop = true;
383
        while (loop)
384
        {
385
            UpdateOptionSelected();
386
            Core.WriteMultiplePositionedLines(
387
                false,
388
                TextAlignment,
389
                Placement,
390
                false,
391
                Line,
392
                _textToDisplay!.ToArray()
393
            );
394

395
            switch (Console.ReadKey(true).Key)
396
            {
397
                case ConsoleKey.Enter:
398
                    SendResponse(
399
                        this,
400
                        new InteractionEventArgs<DialogOption>(Status.Selected, optionSelectedIndex)
401
                    );
402
                    loop = false;
403
                    break;
404

405
                case ConsoleKey.Escape:
406
                    SendResponse(
407
                        this,
408
                        new InteractionEventArgs<DialogOption>(Status.Escaped, DialogOption.None)
409
                    );
410
                    loop = false;
411
                    break;
412

413
                case ConsoleKey.Q:
414
                case ConsoleKey.LeftArrow:
415
                    SwitchOptions();
416
                    break;
417

418
                case ConsoleKey.D:
419
                case ConsoleKey.RightArrow:
420
                    SwitchOptions();
421
                    break;
422

423
                case ConsoleKey.Tab:
424
                    SwitchOptions();
425
                    break;
426
            }
427
        }
428

429
        [Visual]
430
        DialogOption SetDefaultValue()
431
        {
432
            if (_leftOption is null && _rightOption is null)
433
            {
434
                return DialogOption.None;
435
            }
436
            else if (_leftOption is not null && _rightOption is null)
437
            {
438
                return DialogOption.Left;
439
            }
440
            else if (_leftOption is null && _rightOption is not null)
441
            {
442
                return DialogOption.Right;
443
            }
444

445
            return DialogOption.Right;
446
        }
447

448
        [Visual]
449
        void SwitchOptions()
450
        {
451
            if (_leftOption is not null && _rightOption is not null)
452
            {
453
                optionSelectedIndex =
454
                    optionSelectedIndex == DialogOption.Left
455
                        ? DialogOption.Right
456
                        : DialogOption.Left;
457
            }
458
        }
459

460
        [Visual]
461
        void UpdateOptionSelected()
462
        {
463
            if (_leftOption is not null || _rightOption is not null)
464
            {
465
                string optionLine =
466
                    $"{Borders.Vertical} "
467
                    + new string(' ', MaxLineLength)
468
                    + $" {Borders.Vertical}";
469

470
                if (_rightOption is not null && optionSelectedIndex == DialogOption.Right)
471
                {
472
                    int insertPosition = optionLine.Length - 1 - _rightOption.Length - 1 - 2;
473
                    optionLine = optionLine.Remove(insertPosition, _rightOption.Length + 2);
474
                    optionLine = optionLine.Insert(
475
                        insertPosition,
476
                        Core.NEGATIVE_ANCHOR + " " + _rightOption + " " + Core.NEGATIVE_ANCHOR
477
                    );
478
                }
479
                else if (_rightOption is not null && optionSelectedIndex == DialogOption.Left)
480
                {
481
                    int insertPosition = optionLine.Length - 1 - _rightOption.Length - 1 - 2;
482
                    optionLine = optionLine.Remove(insertPosition, _rightOption.Length + 2);
483
                    optionLine = optionLine.Insert(insertPosition, " " + _rightOption + " ");
484
                }
485

486
                if (_leftOption is not null && optionSelectedIndex == DialogOption.Left)
487
                {
488
                    optionLine = optionLine.Remove(2, _leftOption.Length + 2);
489
                    optionLine = optionLine.Insert(
490
                        2,
491
                        Core.NEGATIVE_ANCHOR + " " + _leftOption + " " + Core.NEGATIVE_ANCHOR
492
                    );
493
                }
494
                else if (_leftOption is not null && optionSelectedIndex == DialogOption.Right)
495
                {
496
                    optionLine = optionLine.Remove(2, _leftOption.Length + 2);
497
                    optionLine = optionLine.Insert(2, " " + _leftOption + " ");
498
                }
499

500
                _textToDisplay![^2] = optionLine;
501
            }
502
        }
503
    }
504

505
    #endregion
506
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc