diff --git a/Terminal.Gui/Windows/Dialog.cs b/Terminal.Gui/Windows/Dialog.cs index 5696adbff..996af82a8 100644 --- a/Terminal.Gui/Windows/Dialog.cs +++ b/Terminal.Gui/Windows/Dialog.cs @@ -152,7 +152,7 @@ namespace Terminal.Gui { void LayoutStartedHandler () { - if (buttons.Count == 0) return; + if (buttons.Count == 0 || !IsInitialized) return; int shiftLeft = 0; diff --git a/Terminal.Gui/Windows/Wizard.cs b/Terminal.Gui/Windows/Wizard.cs new file mode 100644 index 000000000..ec4fa3b1e --- /dev/null +++ b/Terminal.Gui/Windows/Wizard.cs @@ -0,0 +1,417 @@ +using System; +using System.Collections.Generic; +using NStack; + +namespace Terminal.Gui { + /// + /// Provides a step-based "wizard" UI. The Wizard supports multiple steps. Each step () can host + /// arbitrary s, much like a . Each step also has a pane for help text. Along the + /// bottom of the Wizard view are customizable buttons enabling the user to navigate forward and backward through the Wizard. + /// + /// + /// + public class Wizard : Dialog { + + /// + /// One step for the Wizard. The view hosts two sub-views: 1) add s to , + /// 2) use to set the contents of the that shows on the + /// right side. Use and to + /// control wether the control or help pane are shown. + /// + /// + /// If s are added, do not set to true as this will conflict + /// with the Next button of the Wizard. + /// + public class WizardStep : View { + /// + /// The title of the . + /// + public ustring Title { get => title; set => title = value; } + // TODO: Update Wizard title when step title is changed if step is current - this will require step to slueth it's parent + private ustring title; + + private View controlPane = new FrameView (); + + /// + /// THe pane that holds the controls for the . Use `Add(View`) to add + /// controls. Note that the Controls view is sized to take 70% of the Wizard's width and the + /// takes the other 30%. This can be adjusted by setting `Width` from `Dim.Percent(70)` to + /// another value. If is set to `false` the control pane will fill the entire + /// Wizard. + /// + public View Controls { get => controlPane; } + + /// + /// Sets or gets help text for the .If is set to + /// `false` the control pane will fill the entire wizard. + /// + /// The help text is displayed using a read-only . + public ustring HelpText { get => helpTextView.Text; set => helpTextView.Text = value; } + private TextView helpTextView = new TextView (); + + /// + /// Sets or gets the text for the back button. The back button will only be visible on + /// steps after the first step. + /// + /// The default text is "Back" + public ustring BackButtonText { get; set; } = ustring.Empty; + // TODO: Update button text of Wizard button when step's button text is changed if step is current - this will require step to slueth it's parent + + /// + /// Sets or gets the text for the next/finish button. + /// + /// The default text is "Next..." if the Pane is not the last pane. Otherwise it is "Finish" + public ustring NextButtonText { get; set; } = ustring.Empty; + // TODO: Update button text of Wizard button when step's button text is changed if step is current - this will require step to slueth it's parent + + /// + /// Initializes a new instance of the class using positioning. + /// + /// Title for the Step. Will be appended to the containing Wizard's title as + /// "Wizard Title - Wizard Step Title" when this step is active. + /// + /// + public WizardStep (ustring title) + { + this.Title = title; // this.Title holds just the "Wizard Title"; base.Title holds "Wizard Title - Step Title" + this.ColorScheme = Colors.Menu; + + Y = 0; + Height = Dim.Fill (1); // for button frame + Width = Dim.Fill (); + + Controls.ColorScheme = Colors.Dialog; + Controls.Border.BorderStyle = BorderStyle.None; + Controls.Border.Padding = new Thickness (0); + Controls.Border.BorderThickness = new Thickness (0); + this.Add (Controls); + + helpTextView.ColorScheme = Colors.Menu; + helpTextView.Y = 0; + helpTextView.ReadOnly = true; + helpTextView.WordWrap = true; + this.Add (helpTextView); + ShowHide (); + + var scrollBar = new ScrollBarView (helpTextView, true); + + scrollBar.ChangedPosition += () => { + helpTextView.TopRow = scrollBar.Position; + if (helpTextView.TopRow != scrollBar.Position) { + scrollBar.Position = helpTextView.TopRow; + } + helpTextView.SetNeedsDisplay (); + }; + + scrollBar.VisibleChanged += () => { + if (scrollBar.Visible && helpTextView.RightOffset == 0) { + helpTextView.RightOffset = 1; + } else if (!scrollBar.Visible && helpTextView.RightOffset == 1) { + helpTextView.RightOffset = 0; + } + }; + + helpTextView.DrawContent += (e) => { + scrollBar.Size = helpTextView.Lines; + scrollBar.Position = helpTextView.TopRow; + if (scrollBar.OtherScrollBarView != null) { + scrollBar.OtherScrollBarView.Size = helpTextView.Maxlength; + scrollBar.OtherScrollBarView.Position = helpTextView.LeftColumn; + } + scrollBar.LayoutSubviews (); + scrollBar.Refresh (); + }; + this.Add (scrollBar); + } + + /// + /// If true (the default) the help will be visible. If false, the help will not be shown and the control pane will + /// fill the wizard step. + /// + public bool ShowHelp { + get => showHelp; + set { + showHelp = value; + ShowHide (); + } + } + private bool showHelp = true; + + /// + /// If true (the default) the View will be visible. If false, the controls will not be shown and the help will + /// fill the wizard step. + /// + public bool ShowControls { + get => showControls; + set { + showControls = value; + ShowHide (); + } + } + private bool showControls = true; + + /// + /// Does the work to show and hide the controls, help, and buttons as appropriate + /// + private void ShowHide () + { + Controls.Height = Dim.Fill (1); + helpTextView.Height = Dim.Fill (1); + helpTextView.Width = Dim.Fill (); + + if (showControls) { + if (showHelp) { + Controls.Width = Dim.Percent (70); + helpTextView.X = Pos.Right (Controls) ; + helpTextView.Width = Dim.Fill (); + + } else { + Controls.Width = Dim.Percent (100); + } + } else { + if (showHelp) { + helpTextView.X = 0; + } else { + // Error - no pane shown + } + + } + Controls.Visible = showControls; + helpTextView.Visible = showHelp; + } + } + + /// + /// If the is not the first step in the wizard, this button causes + /// the event to be fired and the wizard moves to the previous step. + /// + /// + /// Use the event to be notified when the user attempts to go back. + /// + public Button BackButton { get => backBtn; } + private Button backBtn; + + /// + /// If the is the last step in the wizard, this button causes + /// the event to be fired and the wizard to close. If the step is not the last step, + /// the event will be fired and the wizard will move next step. + /// + /// + /// Use the and events to be notified + /// when the user attempts go to the next step or finish the wizard. + /// + public Button NextFinishButton { get => nextfinishBtn; } + private Button nextfinishBtn; + + /// + /// Initializes a new instance of the class using positioning. + /// + /// + /// The Wizard will be vertically and horizontally centered in the container. + /// After initialization use X, Y, Width, and Height change size and position. + /// + public Wizard () : this (ustring.Empty) + { + } + + /// + /// Initializes a new instance of the class using positioning. + /// + /// Title for the Wizard. + /// + /// The Wizard will be vertically and horizontally centered in the container. + /// After initialization use X, Y, Width, and Height change size and position. + /// + public Wizard (ustring title) : base (title) + { + wizardTitle = title; + // Using Justify causes the Back and Next buttons to be hard justified against + // the left and right edge + ButtonAlignment = ButtonAlignments.Justify; + this.Border.BorderStyle = BorderStyle.Double; + + // Add a horiz separator + var separator = new LineView (Graphs.Orientation.Horizontal) { + Y = Pos.AnchorEnd (2) + }; + Add (separator); + + backBtn = new Button ("_Back") { AutoSize = true }; + AddButton (backBtn); + + nextfinishBtn = new Button ("_Next...") { AutoSize = true }; + nextfinishBtn.IsDefault = true; + AddButton (nextfinishBtn); + + backBtn.Clicked += () => { + var args = new WizardStepEventArgs (); + MovingBack?.Invoke (args); + if (!args.Cancel) { + if (currentStep > 0) { + CurrentStep--; + } + } + }; + + nextfinishBtn.Clicked += () => { + if (currentStep == steps.Count - 1) { + var args = new WizardStepEventArgs (); + Finished?.Invoke (args); + if (!args.Cancel) { + Application.RequestStop (this); + } + } else { + var args = new WizardStepEventArgs (); + MovingNext?.Invoke (args); + if (!args.Cancel) { + CurrentStep++; + } + } + }; + + Loaded += () => { + foreach (var step in steps) { + step.Y = 0; + } + if (steps.Count > 0) { + + CurrentStep = 0; + } + }; + + } + + private List steps = new List (); + private int currentStep = 0; + + /// + /// Adds a step to the wizard. The Next and Back buttons navigate through the added steps in the + /// order they were added. + /// + /// + /// The "Next..." button of the last step added will read "Finish" (unless changed from default). + public void AddStep (WizardStep newStep) + { + steps.Add (newStep); + this.Add (newStep); + } + + /// + /// The title of the Wizard, shown at the top of the Wizard with " - currentStep.Title" appended. + /// + public new ustring Title { + get { + // The base (Dialog) Title holds the full title ("Wizard Title - Step Title") + return base.Title; + } + set { + wizardTitle = value; + base.Title = $"{wizardTitle}{(steps.Count > 0 ? " - " + steps [currentStep].Title : string.Empty)}"; + } + } + private ustring wizardTitle = ustring.Empty; + + /// + /// for transition events. + /// + public class WizardStepEventArgs : EventArgs { + /// + /// Set to true to cancel the transition to the next step. + /// + public bool Cancel { get; set; } + + /// + /// Initializes a new instance of + /// + public WizardStepEventArgs () + { + Cancel = false; + } + } + + /// + /// This event is raised when the Back button in the is clicked. The Back button is always + /// the first button in the array of Buttons passed to the constructor, if any. + /// + public event Action MovingBack; + + /// + /// This event is raised when the Next/Finish button in the is clicked. The Next/Finish button is always + /// the last button in the array of Buttons passed to the constructor, if any. This event is only + /// raised if the is the last Step in the Wizard flow + /// (otherwise the event is raised). + /// + public event Action MovingNext; + + /// + /// This event is raised when the Next/Finish button in the is clicked. The Next/Finish button is always + /// the last button in the array of Buttons passed to the constructor, if any. This event is only + /// raised if the is the last Step in the Wizard flow + /// (otherwise the event is raised). + /// + public event Action Finished; + + /// + /// This event is raised when the current step )) in the changes. + /// + public event Action CurrentStepChanged; + + /// + /// for events. + /// + public class CurrentStepChangedEventArgs : EventArgs { + /// + /// The new current . + /// + public int CurrentStepIndex { get; } + + /// + /// Initializes a new instance of + /// + /// The new current . + public CurrentStepChangedEventArgs (int currentStepIndex) + { + CurrentStepIndex = currentStepIndex; + } + } + + /// + /// Gets or sets the currently active . + /// + public int CurrentStep { + get => currentStep; + set { + currentStep = value; + OnCurrentStepChanged (); + } + } + + /// + /// Called when the current has changed (). + /// + public virtual void OnCurrentStepChanged () + { + CurrentStepChanged?.Invoke (new CurrentStepChangedEventArgs (currentStep)); + // Hide all but the first step + foreach (WizardStep step in steps) { + step.Visible = (steps [currentStep] == step); + } + + // TODO: Add support for "Wizard Title - Step Title" + base.Title = $"{wizardTitle}{(steps.Count > 0 ? " - " + steps [currentStep].Title : string.Empty)}"; + + backBtn.Text = steps [currentStep].BackButtonText != ustring.Empty ? steps [currentStep].BackButtonText : "_Back"; + if (currentStep == 0) { + backBtn.Visible = false; + } else { + backBtn.Visible = true; + } + + if (currentStep == steps.Count - 1) { + nextfinishBtn.Text = steps [currentStep].NextButtonText != ustring.Empty ? steps [currentStep].NextButtonText : "Fi_nish"; + } else { + nextfinishBtn.Text = steps [currentStep].NextButtonText != ustring.Empty ? steps [currentStep].NextButtonText : "_Next..."; + } + } + } +} \ No newline at end of file diff --git a/UICatalog/Properties/launchSettings.json b/UICatalog/Properties/launchSettings.json index 5f83420e7..37390f995 100644 --- a/UICatalog/Properties/launchSettings.json +++ b/UICatalog/Properties/launchSettings.json @@ -6,6 +6,18 @@ "UICatalog : -usc": { "commandName": "Project", "commandLineArgs": "-usc" + }, + "Wizards": { + "commandName": "Project", + "commandLineArgs": "Wizards" + }, + "Dialogs": { + "commandName": "Project", + "commandLineArgs": "Dialogs" + }, + "Buttons": { + "commandName": "Project", + "commandLineArgs": "Buttons" } } } \ No newline at end of file diff --git a/UICatalog/Scenarios/Dialogs.cs b/UICatalog/Scenarios/Dialogs.cs index 6d43fd7ec..12a5e27f2 100644 --- a/UICatalog/Scenarios/Dialogs.cs +++ b/UICatalog/Scenarios/Dialogs.cs @@ -174,6 +174,13 @@ namespace UICatalog.Scenarios { }; buttons.Add (button); } + if (buttons.Count > 1) { + buttons [1].Text = "Accept"; + buttons [1].IsDefault = true; + buttons [0].Visible = false; + buttons [0].Text = "_Back"; + buttons [0].IsDefault = false; + } // This tests dynamically adding buttons; ensuring the dialog resizes if needed and // the buttons are laid out correctly @@ -202,7 +209,9 @@ namespace UICatalog.Scenarios { }; buttons.Add (button); dialog.AddButton (button); - button.TabIndex = buttons [buttons.Count - 2].TabIndex + 1; + if (buttons.Count > 1) { + button.TabIndex = buttons [buttons.Count - 2].TabIndex + 1; + } }; dialog.Add (add); diff --git a/UICatalog/Scenarios/Wizards.cs b/UICatalog/Scenarios/Wizards.cs new file mode 100644 index 000000000..8ec9ca9cc --- /dev/null +++ b/UICatalog/Scenarios/Wizards.cs @@ -0,0 +1,237 @@ +using NStack; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Terminal.Gui; + +namespace UICatalog.Scenarios { + [ScenarioMetadata (Name: "Wizards", Description: "Demonstrates how to the Wizard class")] + [ScenarioCategory ("Dialogs")] + public class Wizards : Scenario { + public override void Setup () + { + Win.ColorScheme = Colors.Base; + var frame = new FrameView ("Wizard Options") { + X = Pos.Center (), + Y = 0, + Width = Dim.Percent (75), + Height = 10, + ColorScheme = Colors.Base, + }; + Win.Add (frame); + + var label = new Label ("Width:") { + X = 0, + Y = 0, + Width = 15, + Height = 1, + TextAlignment = Terminal.Gui.TextAlignment.Right, + }; + frame.Add (label); + var widthEdit = new TextField ("80") { + X = Pos.Right (label) + 1, + Y = Pos.Top (label), + Width = 5, + Height = 1 + }; + frame.Add (widthEdit); + + label = new Label ("Height:") { + X = 0, + Y = Pos.Bottom (label), + Width = Dim.Width (label), + Height = 1, + TextAlignment = Terminal.Gui.TextAlignment.Right, + }; + frame.Add (label); + var heightEdit = new TextField ("20") { + X = Pos.Right (label) + 1, + Y = Pos.Top (label), + Width = 5, + Height = 1 + }; + frame.Add (heightEdit); + + label = new Label ("Title:") { + X = 0, + Y = Pos.Bottom (label), + Width = Dim.Width (label), + Height = 1, + TextAlignment = Terminal.Gui.TextAlignment.Right, + }; + frame.Add (label); + var titleEdit = new TextField ("Title") { + X = Pos.Right (label) + 1, + Y = Pos.Top (label), + Width = Dim.Fill (), + Height = 1 + }; + frame.Add (titleEdit); + + void Top_Loaded () + { + frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + 2; + Top.Loaded -= Top_Loaded; + } + Top.Loaded += Top_Loaded; + + label = new Label ("Action:") { + X = Pos.Center (), + Y = Pos.AnchorEnd (1), + AutoSize = true, + TextAlignment = Terminal.Gui.TextAlignment.Right, + }; + Win.Add (label); + var actionLabel = new Label (" ") { + X = Pos.Right (label), + Y = Pos.AnchorEnd (1), + AutoSize = true, + ColorScheme = Colors.Error, + }; + + var showWizardButton = new Button ("Show Wizard") { + X = Pos.Center (), + Y = Pos.Bottom (frame) + 2, + IsDefault = true, + }; + showWizardButton.Clicked += () => { + try { + int width = 0; + int.TryParse (widthEdit.Text.ToString (), out width); + int height = 0; + int.TryParse (heightEdit.Text.ToString (), out height); + + if (width < 1 || height < 1) { + MessageBox.ErrorQuery ("Nope", "Height and width must be greater than 0 (much bigger)", "Ok"); + return; + } + + var wizard = new Wizard (titleEdit.Text) { + Width = width, + Height = height + }; + + wizard.MovingBack += (args) => { + //args.Cancel = true; + actionLabel.Text = "Moving Back"; + }; + + wizard.MovingNext += (args) => { + //args.Cancel = true; + actionLabel.Text = "Moving Next"; + }; + + wizard.Finished += (args) => { + //args.Cancel = true; + actionLabel.Text = "Finished"; + }; + + // Add 1st step + var firstStep = new Wizard.WizardStep ("End User License Agreement"); + wizard.AddStep (firstStep); + firstStep.ShowControls = false; + firstStep.NextButtonText = "Accept!"; + firstStep.HelpText = "This is the End User License Agreement.\n\n\n\n\n\nThis is a test of the emergency broadcast system. This is a test of the emergency broadcast system.\nThis is a test of the emergency broadcast system.\n\n\nThis is a test of the emergency broadcast system.\n\nThis is a test of the emergency broadcast system.\n\n\n\nThe end of the EULA."; + + // Add 2nd step + var secondStep = new Wizard.WizardStep ("Second Step"); + wizard.AddStep (secondStep); + secondStep.HelpText = "This is the help text for the Second Step.\n\nPress the button to see a message box.\n\nEnter name too."; + var buttonLbl = new Label () { Text = "Second Step Button: ", AutoSize = true, X = 1, Y = 1 }; + var button = new Button () { + Text = "Press Me", + X = Pos.Right (buttonLbl), + Y = Pos.Top (buttonLbl) + }; + button.Clicked += () => { + MessageBox.Query ("Wizard Scenario", "The Second Step Button was pressed."); + }; + secondStep.Controls.Add (buttonLbl, button); + var lbl = new Label () { Text = "First Name: ", AutoSize = true, X = 1, Y = Pos.Bottom (buttonLbl) }; + var firstNameField = new TextField () { Width = 30, X = Pos.Right (lbl), Y = Pos.Top (lbl) }; + secondStep.Controls.Add (lbl, firstNameField); + lbl = new Label () { Text = "Last Name: ", AutoSize = true, X = 1, Y = Pos.Bottom (lbl) }; + var lastNameField = new TextField () { Width = 30, X = Pos.Right (lbl), Y = Pos.Top (lbl) }; + secondStep.Controls.Add (lbl, lastNameField); + + // Add 3rd step + var thirdStep = new Wizard.WizardStep ("Third Step"); + wizard.AddStep (thirdStep); + thirdStep.HelpText = "This is the help text for the Third Step."; + var progLbl = new Label () { Text = "Third Step ProgressBar: ", AutoSize = true, X = 1, Y = 10 }; + var progressBar = new ProgressBar () { + X = Pos.Right (progLbl), + Y = Pos.Top (progLbl), + Width = 40, + Fraction = 0.42F + }; + thirdStep.Controls.Add (progLbl, progressBar); + + // Add 4th step + var fourthStep = new Wizard.WizardStep ("Hidden Help pane"); + wizard.AddStep (fourthStep); + fourthStep.ShowHelp = false; + var someText = new TextView () { + Text = "This step shows how to hide the Help pane. The control pane contains this TextView.", + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill (), + WordWrap = true, + }; + fourthStep.Controls.Add (someText); + var scrollBar = new ScrollBarView (someText, true); + + scrollBar.ChangedPosition += () => { + someText.TopRow = scrollBar.Position; + if (someText.TopRow != scrollBar.Position) { + scrollBar.Position = someText.TopRow; + } + someText.SetNeedsDisplay (); + }; + + scrollBar.VisibleChanged += () => { + if (scrollBar.Visible && someText.RightOffset == 0) { + someText.RightOffset = 1; + } else if (!scrollBar.Visible && someText.RightOffset == 1) { + someText.RightOffset = 0; + } + }; + + someText.DrawContent += (e) => { + scrollBar.Size = someText.Lines; + scrollBar.Position = someText.TopRow; + if (scrollBar.OtherScrollBarView != null) { + scrollBar.OtherScrollBarView.Size = someText.Maxlength; + scrollBar.OtherScrollBarView.Position = someText.LeftColumn; + } + scrollBar.LayoutSubviews (); + scrollBar.Refresh (); + }; + fourthStep.Controls.Add (scrollBar); + + // Add last step + var lastStep = new Wizard.WizardStep ("The last step"); + wizard.AddStep (lastStep); + lastStep.HelpText = "The wizard is complete! Press the Finish button to continue. Pressing ESC will cancel the wizard."; + + + // TODO: Demo setting initial Pane + + wizard.Finished += (args) => { + Application.RequestStop (wizard); + }; + + Application.Run (wizard); + + } catch (FormatException) { + actionLabel.Text = "Invalid Options"; + } + }; + Win.Add (showWizardButton); + + Win.Add (actionLabel); + } + } +} diff --git a/UnitTests/DialogTests.cs b/UnitTests/DialogTests.cs index 1ac792a93..e25cdcc94 100644 --- a/UnitTests/DialogTests.cs +++ b/UnitTests/DialogTests.cs @@ -19,10 +19,10 @@ namespace Terminal.Gui.Views { this.output = output; } - private Application.RunState RunButtonTestDialog (string title, int width, Dialog.ButtonAlignments align, params Button [] btns) + private (Application.RunState, Dialog) RunButtonTestDialog (string title, int width, Dialog.ButtonAlignments align, params Button [] btns) { var dlg = new Dialog (title, width, 3, btns) { ButtonAlignment = align }; - return Application.Begin (dlg); + return (Application.Begin (dlg), dlg); } [Fact] @@ -30,6 +30,7 @@ namespace Terminal.Gui.Views { public void ButtonAlignment_One () { var d = ((FakeDriver)Application.Driver); + Application.RunState runstate = null; var title = "1234"; // E.g "|[ ok ]|" @@ -41,28 +42,28 @@ namespace Terminal.Gui.Views { d.SetBufferSize (width, 3); - var runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Justify buttonRow = $"{d.VLine} {d.LeftBracket} {btnText} {d.RightBracket}{d.VLine}"; Assert.Equal (width, buttonRow.Length); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btnText)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btnText)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Right buttonRow = $"{d.VLine} {d.LeftBracket} {btnText} {d.RightBracket}{d.VLine}"; Assert.Equal (width, buttonRow.Length); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btnText)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btnText)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Left buttonRow = $"{d.VLine}{d.LeftBracket} {btnText} {d.RightBracket} {d.VLine}"; Assert.Equal (width, buttonRow.Length); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btnText)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btnText)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); } @@ -71,6 +72,8 @@ namespace Terminal.Gui.Views { [AutoInitShutdown] public void ButtonAlignment_Two () { + Application.RunState runstate = null; + var d = ((FakeDriver)Application.Driver); var title = "1234"; @@ -87,36 +90,100 @@ namespace Terminal.Gui.Views { d.SetBufferSize (buttonRow.Length, 3); - var runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Justify buttonRow = $@"{d.VLine}{btn1} {btn2}{d.VLine}"; Assert.Equal (width, buttonRow.Length); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Right buttonRow = $@"{d.VLine} {btn1} {btn2}{d.VLine}"; Assert.Equal (width, buttonRow.Length); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Left buttonRow = $@"{d.VLine}{btn1} {btn2} {d.VLine}"; Assert.Equal (width, buttonRow.Length); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); } + [Fact] + [AutoInitShutdown] + public void ButtonAlignment_Two_Hidden () + { + Application.RunState runstate = null; + bool firstIteration = false; + + var d = ((FakeDriver)Application.Driver); + + var title = "1234"; + // E.g "|[ yes ][ no ]|" + var btn1Text = "yes"; + var btn1 = $"{d.LeftBracket} {btn1Text} {d.RightBracket}"; + var btn2Text = "no"; + var btn2 = $"{d.LeftBracket} {btn2Text} {d.RightBracket}"; + + var buttonRow = $@"{d.VLine} {btn1} {btn2} {d.VLine}"; + var width = buttonRow.Length; + var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐"; + var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘"; + + d.SetBufferSize (buttonRow.Length, 3); + + Dialog dlg = null; + Button button1, button2; + + //// Default (Center) + //button1 = new Button (btn1Text); + //button2 = new Button (btn2Text); + //(runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, button1, button2); + //button1.Visible = false; + //Application.RunMainLoopIteration (ref runstate, true, ref firstIteration); + //buttonRow = $@"{d.VLine} {btn2} {d.VLine}"; + //GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); + //Application.End (runstate); + + // Justify + Assert.Equal (width, buttonRow.Length); + button1 = new Button (btn1Text); + button2 = new Button (btn2Text); + (runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, button1, button2); + button1.Visible = false; + Application.RunMainLoopIteration (ref runstate, true, ref firstIteration); + buttonRow = $@"{d.VLine} {btn2}{d.VLine}"; + GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); + Application.End (runstate); + + //// Right + //buttonRow = $@"{d.VLine} {btn1} {btn2}{d.VLine}"; + //Assert.Equal (width, buttonRow.Length); + //(runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text)); + //GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); + //Application.End (runstate); + + //// Left + //buttonRow = $@"{d.VLine}{btn1} {btn2} {d.VLine}"; + //Assert.Equal (width, buttonRow.Length); + //(runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text)); + //GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); + //Application.End (runstate); + } + [Fact] [AutoInitShutdown] public void ButtonAlignment_Three () { + Application.RunState runstate = null; + var d = ((FakeDriver)Application.Driver); var title = "1234"; @@ -135,28 +202,28 @@ namespace Terminal.Gui.Views { d.SetBufferSize (buttonRow.Length, 3); - var runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Justify buttonRow = $@"{d.VLine}{btn1} {btn2} {btn3}{d.VLine}"; Assert.Equal (width, buttonRow.Length); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Right buttonRow = $@"{d.VLine} {btn1} {btn2} {btn3}{d.VLine}"; Assert.Equal (width, buttonRow.Length); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Left buttonRow = $@"{d.VLine}{btn1} {btn2} {btn3} {d.VLine}"; Assert.Equal (width, buttonRow.Length); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); } @@ -165,6 +232,8 @@ namespace Terminal.Gui.Views { [AutoInitShutdown] public void ButtonAlignment_Four () { + Application.RunState runstate = null; + var d = ((FakeDriver)Application.Driver); var title = "1234"; @@ -186,28 +255,28 @@ namespace Terminal.Gui.Views { d.SetBufferSize (buttonRow.Length, 3); // Default - Center - var runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Justify buttonRow = $"{d.VLine}{btn1} {btn2} {btn3} {btn4}{d.VLine}"; Assert.Equal (width, buttonRow.Length); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Right buttonRow = $"{d.VLine} {btn1} {btn2} {btn3} {btn4}{d.VLine}"; Assert.Equal (width, buttonRow.Length); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Left buttonRow = $"{d.VLine}{btn1} {btn2} {btn3} {btn4} {d.VLine}"; Assert.Equal (width, buttonRow.Length); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); } @@ -216,6 +285,8 @@ namespace Terminal.Gui.Views { [AutoInitShutdown] public void ButtonAlignment_Four_Wider () { + Application.RunState runstate = null; + var d = ((FakeDriver)Application.Driver); var title = "1234"; @@ -240,28 +311,28 @@ namespace Terminal.Gui.Views { d.SetBufferSize (width, 3); // Default - Center - var runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Justify buttonRow = $"{d.VLine}{btn1} {btn2} {btn3} {btn4}{d.VLine}"; Assert.Equal (width, ustring.Make (buttonRow).ConsoleWidth); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Right buttonRow = $"{d.VLine} {btn1} {btn2} {btn3} {btn4}{d.VLine}"; Assert.Equal (width, ustring.Make (buttonRow).ConsoleWidth); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Left buttonRow = $"{d.VLine}{btn1} {btn2} {btn3} {btn4} {d.VLine}"; Assert.Equal (width, ustring.Make (buttonRow).ConsoleWidth); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); } @@ -270,6 +341,8 @@ namespace Terminal.Gui.Views { [AutoInitShutdown] public void ButtonAlignment_Four_WideOdd () { + Application.RunState runstate = null; + var d = ((FakeDriver)Application.Driver); var title = "1234"; @@ -293,28 +366,28 @@ namespace Terminal.Gui.Views { d.SetBufferSize (buttonRow.Length, 3); // Default - Center - var runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Justify buttonRow = $"{d.VLine}{btn1} {btn2} {btn3} {btn4}{d.VLine}"; Assert.Equal (width, buttonRow.Length); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Right buttonRow = $"{d.VLine} {btn1} {btn2} {btn3} {btn4}{d.VLine}"; Assert.Equal (width, buttonRow.Length); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); // Left buttonRow = $"{d.VLine}{btn1} {btn2} {btn3} {btn4} {d.VLine}"; Assert.Equal (width, buttonRow.Length); - runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); } @@ -323,6 +396,8 @@ namespace Terminal.Gui.Views { [AutoInitShutdown] public void Zero_Buttons_Works () { + Application.RunState runstate = null; + var d = ((FakeDriver)Application.Driver); var title = "1234"; @@ -333,7 +408,7 @@ namespace Terminal.Gui.Views { var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘"; d.SetBufferSize (buttonRow.Length, 3); - var runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, null); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, null); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); @@ -343,6 +418,8 @@ namespace Terminal.Gui.Views { [AutoInitShutdown] public void One_Button_Works () { + Application.RunState runstate = null; + var d = ((FakeDriver)Application.Driver); var title = "1234"; @@ -354,7 +431,7 @@ namespace Terminal.Gui.Views { var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘"; d.SetBufferSize (buttonRow.Length, 3); - var runstate = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText)); + (runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText)); GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); Application.End (runstate); } @@ -363,6 +440,8 @@ namespace Terminal.Gui.Views { [AutoInitShutdown] public void Add_Button_Works () { + Application.RunState runstate = null; + var d = ((FakeDriver)Application.Driver); var title = "1234"; @@ -380,7 +459,7 @@ namespace Terminal.Gui.Views { // Default (center) var dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Center }; - var runstate = Application.Begin (dlg); + runstate = Application.Begin (dlg); var buttonRow = $"{d.VLine} {btn1} {d.VLine}"; GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output); diff --git a/UnitTests/WizardTests.cs b/UnitTests/WizardTests.cs new file mode 100644 index 000000000..43aec149f --- /dev/null +++ b/UnitTests/WizardTests.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using Terminal.Gui; +using Xunit; +using System.Globalization; +using Xunit.Abstractions; +using NStack; + +namespace Terminal.Gui.Views { + + public class WizardTests { + readonly ITestOutputHelper output; + + public WizardTests (ITestOutputHelper output) + { + this.output = output; + } + + private void RunButtonTestWizard (string title, int width, int height) + { + var wizard = new Wizard (title) { Width = width, Height = height }; + Application.End (Application.Begin (wizard)); + } + + // =========== WizardStep Tests + [Fact, AutoInitShutdown] + public void WizardStep_Title () + { + // Verify default title + + // Verify set actually changes property + + // Verify set changes Wizard title (TODO: NOT YET IMPLEMENTED) + } + + [Fact, AutoInitShutdown] + public void WizardStep_ButtonText () + { + // Verify default button text + + // Verify set actually changes property + + // Verify set actually changes buttons for the current step + } + + // =========== Wizard Tests + [Fact, AutoInitShutdown] + public void DefaultConstructor_SizedProperly () + { + var d = ((FakeDriver)Application.Driver); + + var wizard = new Wizard (); + Assert.NotEqual (0, wizard.Width); + Assert.NotEqual (0, wizard.Height); + } + + [Fact, AutoInitShutdown] + // Verify a zero-step wizard doesn't crash and shows a blank wizard + // and that the title is correct + public void ZeroStepWizard_Shows () + { + var d = ((FakeDriver)Application.Driver); + + var title = "1234"; + var stepTitle = ""; + + int width = 30; + int height = 6; + d.SetBufferSize (width, height); + + var btnBackText = "Back"; + var btnBack = $"{d.LeftBracket} {btnBackText} {d.RightBracket}"; + var btnNextText = "Next..."; + var btnNext = $"{d.LeftBracket}{d.LeftDefaultIndicator} {btnNextText} {d.RightDefaultIndicator}{d.RightBracket}"; + + var topRow = $"{d.ULDCorner} {title}{stepTitle} {new String (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 4)}{d.URDCorner}"; + var row2 = $"{d.VDLine}{new String (' ', width - 2)}{d.VDLine}"; + var row3 = row2; + var separatorRow = $"{d.VDLine}{new String (d.HLine.ToString () [0], width - 2)}{d.VDLine}"; + var buttonRow = $"{d.VDLine}{btnBack}{new String (' ', width - btnBack.Length - btnNext.Length - 2)}{btnNext}{d.VDLine}"; + var bottomRow = $"{d.LLDCorner}{new String (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}"; + + var wizard = new Wizard (title) { Width = width, Height = height }; + Application.End (Application.Begin (wizard)); + GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{row2}\n{row3}\n{separatorRow}\n{buttonRow}\n{bottomRow}", output); + } + + [Fact, AutoInitShutdown] + // This test verifies that a single step wizard shows the correct buttons + // and that the title is correct + public void OneStepWizard_Shows () + { + } + + [Fact, AutoInitShutdown] + // This test verifies that the 2nd step in a wizard with 2 steps + // shows the correct buttons on both steps + // and that the title is correct + public void TwoStepWizard_Next_Shows_SecondStep () + { + // verify step one + + // Next + + // verify step two + + // Back + + // verify step one again + } + + [Fact, AutoInitShutdown] + // This test verifies that the 2nd step in a wizard with more than 2 steps + // shows the correct buttons on all steps + // and that the title is correct + public void ThreeStepWizard_Next_Shows_Steps () + { + + // verify step one + + // Next + + // verify step two + + // Back + + // verify step one again + } + + [Fact, AutoInitShutdown] + // this test is needed because Wizard overrides Dialog's title behavior ("Title - StepTitle") + public void Setting_Title_Works () + { + var d = ((FakeDriver)Application.Driver); + + var title = "1234"; + var stepTitle = " - ABCD"; + + int width = 40; + int height = 4; + d.SetBufferSize (width, height); + + var btnNextText = "Finish"; + var btnNext = $"{d.LeftBracket}{d.LeftDefaultIndicator} {btnNextText} {d.RightDefaultIndicator}{d.RightBracket}"; + + var topRow = $"{d.ULDCorner} {title}{stepTitle} {new String (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 4)}{d.URDCorner}"; + var separatorRow = $"{d.VDLine}{new String (d.HLine.ToString () [0], width - 2)}{d.VDLine}"; + + // Once this is fixed, revert to commented out line: https://github.com/migueldeicaza/gui.cs/issues/1791 + var buttonRow = $"{d.VDLine}{new String (' ', width - btnNext.Length - 3)}{btnNext} {d.VDLine}"; + //var buttonRow = $"{d.VDLine}{new String (' ', width - btnNext.Length - 2)}{btnNext}{d.VDLine}"; + var bottomRow = $"{d.LLDCorner}{new String (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}"; + + var wizard = new Wizard (title) { Width = width, Height = height }; + wizard.AddStep (new Wizard.WizardStep ("ABCD")); + + Application.End (Application.Begin (wizard)); + GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{separatorRow}\n{buttonRow}\n{bottomRow}", output); + } + } +} \ No newline at end of file