using Annulus; using Azimuth; using Clave; using System; using System.Collections.Generic; using UnitTestSharp; namespace Glass.UnitTests { public class MessageBoxTests : TestFixture { [Flags] public enum TestAnswer { Yes = 1, No = 2, Cancel = 4, } public enum NotFlags { Only = 1, } [Flags] public enum WideAnswer : ulong { Low = 1, High = 1UL << 63, } [Flags] public enum SignedAnswer : sbyte { Low = 1, Sign = -128, } /// /// One line of this is 20 high. /// public static readonly TextStyle LabelStyle = TextStyleTests.WithFont() with { VerticalMargin = 8 }; /// /// Spacing of 4, a message in the label style, and a 50 by 20 button for each of the first three bits. /// public static readonly MessageBoxMaterial Measurements = new MessageBoxMaterial { TextStyle = LabelStyle, Spacing = 4, Buttons = new[] { ButtonOf(46, 16), ButtonOf(46, 16), ButtonOf(46, 16) }, }; /// /// A button whose body wants exactly this much room, inside a 2 thick border. /// public static ButtonMaterial ButtonOf(Scalar width, Scalar height) { Material body = new ScrollViewTests.SizedSolidMaterial(new Vector(width, height)); return new ButtonMaterial(ScrollViewTests.Bordered(2, "button").Framing(body)); } public static Vector TextSize(string text) { return new TextMaterial { Text = text, Style = LabelStyle }.PreferredSize(Constraints.Unbounded); } public static Rect Box(Scalar x, Scalar y, Scalar width, Scalar height) { return Rect.FromPositionAndSize(new Vector(x, y), new Vector(width, height)); } public static MessageBox NewBox( TestAnswer buttons = TestAnswer.Yes | TestAnswer.No, string message = "hello", MessageBoxMaterial? material = null) { var returnMe = new MessageBox(buttons, DialogBehavior.Default); returnMe.MessageBoxMaterial = material ?? Measurements; returnMe.Message = message; return returnMe; } public static void Click(Button button) { button.Click(default, MouseButtonFlags.Left, 1); } [IgnoreTest] public static Control MessageOf(MessageBox box) where TAnswer : struct, Enum { foreach (Control child in box.CanvasChildren.BackToFront()) { if (!(child is Button)) { return child; } } throw new InvalidOperationException("The box holds no message."); } [IgnoreTest] public static string MessageTextOf(MessageBox box) where TAnswer : struct, Enum { return ((Label)MessageOf(box)).Text.ToString(); } public class CtorTests : TestFixture { public void Always_BuildsAButtonForEachBitAskedFor() { var box = new MessageBox(TestAnswer.Yes | TestAnswer.Cancel, DialogBehavior.Default); CheckNotNull(box.ButtonFor(TestAnswer.Yes)); CheckNotNull(box.ButtonFor(TestAnswer.Cancel)); CheckEqual(3, ChildCountOf(box)); } [IgnoreTest] public static int ChildCountOf(Control parent) { int returnMe = 0; foreach (Control child in parent.CanvasChildren.BackToFront()) { ++returnMe; } return returnMe; } public void Always_StartsClosedWithNoWords() { var box = new MessageBox(TestAnswer.Yes, DialogBehavior.Default); CheckFalse(box.IsOpen); CheckEqual("", box.Message); } public void AnAnswerNotAskedFor_HasNoButton() { var box = new MessageBox(TestAnswer.Yes, DialogBehavior.Default); CheckThrow(typeof(ArgumentException)); box.ButtonFor(TestAnswer.No); } public void ABitNoNameCovers_StillGetsAButton() { var box = new MessageBox((TestAnswer)8, DialogBehavior.Default); CheckNotNull(box.ButtonFor((TestAnswer)8)); } public void NoButtons_Throws() { CheckThrow(typeof(ArgumentException)); new MessageBox(0, DialogBehavior.Default); } public void AnEnumWithoutFlags_Throws() { CheckThrow(typeof(ArgumentException)); new MessageBox(NotFlags.Only, DialogBehavior.Default); } public void TheButtons_RunLeftToRightByBit() { MessageBox box = NewBox(TestAnswer.Cancel | TestAnswer.Yes | TestAnswer.No); CheckTrue(box.ButtonFor(TestAnswer.Yes).Bounds.Right <= box.ButtonFor(TestAnswer.No).Bounds.Left); CheckTrue(box.ButtonFor(TestAnswer.No).Bounds.Right <= box.ButtonFor(TestAnswer.Cancel).Bounds.Left); } public void TheTopBitOfAWideEnum_GetsAButton() { var box = new MessageBox(WideAnswer.Low | WideAnswer.High, DialogBehavior.Default); CheckNotNull(box.ButtonFor(WideAnswer.High)); } public void TheSignBitOfASignedEnum_GetsAButton() { var box = new MessageBox(SignedAnswer.Sign, DialogBehavior.Default); CheckNotNull(box.ButtonFor(SignedAnswer.Sign)); } } public class MessageTests : TestFixture { public void Setting_ShowsTheWords() { MessageBox box = NewBox(message: "one"); box.Message = "two"; CheckEqual("two", box.Message); CheckEqual("two", MessageTextOf(box)); } public void Null_Throws() { MessageBox box = NewBox(); CheckThrow(typeof(ArgumentNullException)); box.Message = null; } } public class MaterialTests : TestFixture { public void Setting_GivesEachButtonTheLookAtItsBit() { var looks = new[] { DropdownTests.Arrow(1, 1), DropdownTests.Arrow(2, 2), DropdownTests.Arrow(3, 3) }; var box = new MessageBox(TestAnswer.Yes | TestAnswer.Cancel, DialogBehavior.Default); box.MessageBoxMaterial = Measurements with { Buttons = looks }; CheckEqual(looks[0], box.ButtonFor(TestAnswer.Yes).ButtonMaterial); CheckEqual(looks[2], box.ButtonFor(TestAnswer.Cancel).ButtonMaterial); } public void ALookTheArrayDoesNotReach_LeavesTheButtonUnstyled() { var box = new MessageBox(TestAnswer.Yes | TestAnswer.No, DialogBehavior.Default); box.MessageBoxMaterial = Measurements with { Buttons = new[] { DropdownTests.Arrow(1, 1) } }; CheckEqual(default(ButtonMaterial), box.ButtonFor(TestAnswer.No).ButtonMaterial); } public void NoLooks_LeaveEveryButtonUnstyled() { MessageBox box = NewBox(); box.MessageBoxMaterial = Measurements with { Buttons = null }; CheckEqual(default(ButtonMaterial), box.ButtonFor(TestAnswer.Yes).ButtonMaterial); CheckEqual(default(ButtonMaterial), box.ButtonFor(TestAnswer.No).ButtonMaterial); } public void Getting_ReturnsWhatWasSet() { MessageBox box = NewBox(); MessageBoxMaterial material = Measurements with { Background = DropdownTests.Bordered3, Spacing = 9, }; box.MessageBoxMaterial = material; CheckEqual(material.Background, box.MessageBoxMaterial.Background); CheckEqual(9, box.MessageBoxMaterial.Spacing); CheckTrue(ReferenceEquals(material.TextStyle, box.MessageBoxMaterial.TextStyle)); CheckTrue(ReferenceEquals(material.Buttons, box.MessageBoxMaterial.Buttons)); } public void Setting_GivesTheBoxItsBackground() { MessageBox box = NewBox(); Material background = DropdownTests.Bordered3; box.MessageBoxMaterial = Measurements with { Background = background }; CheckEqual(background, box.Material); } public void Setting_StylesTheMessage() { MessageBox box = NewBox(); TextStyle style = LabelStyle with { VerticalMargin = 3 }; box.MessageBoxMaterial = Measurements with { TextStyle = style }; CheckTrue(ReferenceEquals(style, ((Label)MessageOf(box)).Style)); } public void NoTextStyle_StylesTheMessageWithAnUnstyledOne() { MessageBox box = NewBox(); box.MessageBoxMaterial = Measurements with { TextStyle = null }; CheckNotNull(((Label)MessageOf(box)).Style); CheckEqual(MessageTextOf(box), "hello"); } } public class SizingTests : TestFixture { public void ShortMessage_LeavesTheBoxAsWideAsItsTwoButtonsAndTheGapBetween() { MessageBox box = NewBox(message: ""); CheckEqual(new Vector(112, TextSize("").Y + 32), box.Bounds.Size); } public void ALongMessage_WidensTheBox() { string words = new string('x', 80); MessageBox box = NewBox(message: words); CheckEqual(new Vector(TextSize(words).X + 8, 52), box.Bounds.Size); } public void MoreButtons_WidenTheBox() { MessageBox box = NewBox(TestAnswer.Yes | TestAnswer.No | TestAnswer.Cancel, message: ""); CheckEqual(166, box.Bounds.Width); } public void EveryButton_IsAsWideAsTheWidestOfThem() { var looks = new[] { ButtonOf(46, 16), ButtonOf(70, 16), ButtonOf(46, 16) }; MessageBox box = NewBox( TestAnswer.Yes | TestAnswer.No, material: Measurements with { Buttons = looks }); CheckEqual(74, box.ButtonFor(TestAnswer.Yes).Bounds.Width); CheckEqual(74, box.ButtonFor(TestAnswer.No).Bounds.Width); } public void TheButtonRow_IsAsTallAsTheTallestButton() { var looks = new[] { ButtonOf(46, 16), ButtonOf(46, 36), ButtonOf(46, 16) }; MessageBox box = NewBox( TestAnswer.Yes | TestAnswer.No, message: "", material: Measurements with { Buttons = looks }); CheckEqual(TextSize("").Y + 40 + 12, box.Bounds.Height); CheckEqual(40, box.ButtonFor(TestAnswer.Yes).Bounds.Height); } public void TheBackground_AddsItsChrome() { MessageBox box = NewBox( message: "", material: Measurements with { Background = DropdownTests.Bordered3 }); CheckEqual(new Vector(118, TextSize("").Y + 38), box.Bounds.Size); } public void TheMessageAndButtons_StackInTheBoxesOwnSpace() { MessageBox box = NewBox(); Scalar width = box.Bounds.Width; CheckEqual(Box(width - 54, 28, 50, 20), box.ButtonFor(TestAnswer.No).Bounds); CheckEqual(Box(width - 108, 28, 50, 20), box.ButtonFor(TestAnswer.Yes).Bounds); CheckEqual(Box(4, 4, width - 8, 20), MessageOf(box).Bounds); } public void TheMessageAndButtons_LeaveTheBackgroundsChromeOutOfTheirSpace() { MessageBox box = NewBox( material: Measurements with { Background = DropdownTests.Bordered3 }); Scalar width = box.Bounds.Width - 6; CheckEqual(Box(width - 54, 28, 50, 20), box.ButtonFor(TestAnswer.No).Bounds); CheckEqual(Box(4, 4, width - 8, 20), MessageOf(box).Bounds); } public void ChangingTheMessage_ResizesTheBoxButKeepsItsPlace() { MessageBox box = NewBox(message: ""); box.Bounds = Box(10, 20, box.Bounds.Width, box.Bounds.Height); string words = new string('x', 80); box.Message = words; CheckEqual(Box(10, 20, TextSize(words).X + 8, 52), box.Bounds); } public void ChangingTheMaterial_ResizesTheBox() { MessageBox box = NewBox(message: ""); box.MessageBoxMaterial = Measurements with { Spacing = 10 }; CheckEqual(new Vector(130, TextSize("").Y + 20 + 30), box.Bounds.Size); } public void CenteredByThePopup_AtItsOwnSize() { MessageBox box = NewBox(); box.Open(); DropdownTests.Walk(box); CheckTrue(box.Visible); CheckEqual(box.Bounds.Center, DropdownTests.ScreenRect.Center); } } public class DefaultAnswerTests : TestFixture { public void StartsAsNone() { CheckNull(NewBox().DefaultAnswer); } public void IsKept() { MessageBox box = NewBox(); box.DefaultAnswer = TestAnswer.No; CheckEqual(TestAnswer.No, box.DefaultAnswer); } public void IsTheInitialFocus() { MessageBox box = NewBox(); box.DefaultAnswer = TestAnswer.No; CheckEqual(box.ButtonFor(TestAnswer.No), box.FocusScope.InitialFocus); } public void SettingNone_ClearsTheInitialFocus() { MessageBox box = NewBox(); box.DefaultAnswer = TestAnswer.No; box.DefaultAnswer = null; CheckNull(box.DefaultAnswer); CheckNull(box.FocusScope.InitialFocus); } public void AnAnswerNotAskedFor_ThrowsAndKeepsTheOldOne() { MessageBox box = NewBox(TestAnswer.Yes | TestAnswer.No); box.DefaultAnswer = TestAnswer.Yes; bool threw = false; try { box.DefaultAnswer = TestAnswer.Cancel; } catch (ArgumentException) { threw = true; } Check(threw); CheckEqual(TestAnswer.Yes, box.DefaultAnswer); CheckEqual(box.ButtonFor(TestAnswer.Yes), box.FocusScope.InitialFocus); } } public class CancelAnswerTests : TestFixture { public void StartsAsNone() { CheckNull(NewBox().CancelAnswer); } public void IsKept() { MessageBox box = NewBox(); box.CancelAnswer = TestAnswer.No; CheckEqual(TestAnswer.No, box.CancelAnswer); } public void SettingNone_ClearsIt() { MessageBox box = NewBox(); box.CancelAnswer = TestAnswer.No; box.CancelAnswer = null; CheckNull(box.CancelAnswer); } public void AnAnswerNotAskedFor_ThrowsAndKeepsTheOldOne() { MessageBox box = NewBox(TestAnswer.Yes | TestAnswer.No); box.CancelAnswer = TestAnswer.No; bool threw = false; try { box.CancelAnswer = TestAnswer.Cancel; } catch (ArgumentException) { threw = true; } Check(threw); CheckEqual(TestAnswer.No, box.CancelAnswer); } } public class BehaviorTests : TestFixture { public void IsKept() { var box = new MessageBox(TestAnswer.Yes, DialogBehavior.NoKeys); CheckEqual(DialogBehavior.NoKeys, box.Behavior); } public void Null_Throws() { CheckThrow(typeof(ArgumentNullException)); new MessageBox(TestAnswer.Yes, null); } public void TakesDismissOnOutsidePressFromTheBehavior() { var dismissing = new DialogBehavior(default, default, dismissOnOutsidePress: true); CheckTrue(new MessageBox(TestAnswer.Yes, dismissing).DismissOnOutsidePress); CheckFalse(new MessageBox(TestAnswer.Yes, DialogBehavior.Default).DismissOnOutsidePress); } public void IsAFocusScope() { CheckNotNull(NewBox().FocusScope); } public void ItsKeyChordsAreTheBehaviors() { CheckEqual(DialogBehavior.Default.KeyChords, NewBox().KeyChords); } } public class KeyboardTests : TestFixture { [IgnoreTest] public static FocusedKeys Entered(MessageBox box) { box.Open(); box.Visible = true; return FocusedKeys.Enter(box); } public void Enter_GivesTheDefaultAnswer() { MessageBox box = NewBox(TestAnswer.Yes | TestAnswer.No | TestAnswer.Cancel); box.DefaultAnswer = TestAnswer.Yes; List events = AnswerTests.Record(box); FocusedKeys keys = Entered(box); keys.Manager.ClearFocus(); CheckTrue(keys.Press(KeyCode.MainEnter)); CheckEqual(new[] { "Yes", "closed" }, events.ToArray()); } public void NumpadEnter_GivesTheDefaultAnswer() { MessageBox box = NewBox(); box.DefaultAnswer = TestAnswer.No; List events = AnswerTests.Record(box); FocusedKeys keys = Entered(box); keys.Manager.ClearFocus(); keys.Press(KeyCode.NumpadEnter); CheckEqual(new[] { "No", "closed" }, events.ToArray()); } public void Escape_GivesTheCancelAnswer() { MessageBox box = NewBox(TestAnswer.Yes | TestAnswer.No | TestAnswer.Cancel); box.CancelAnswer = TestAnswer.Cancel; List events = AnswerTests.Record(box); FocusedKeys keys = Entered(box); CheckTrue(keys.Press(KeyCode.Escape)); CheckEqual(new[] { "Cancel", "closed" }, events.ToArray()); } public void Escape_ClosesTheBoxAndLeavesNoActiveScope() { MessageBox box = NewBox(TestAnswer.Yes | TestAnswer.No); box.CancelAnswer = TestAnswer.No; FocusedKeys keys = Entered(box); keys.Press(KeyCode.Escape); CheckFalse(box.IsOpen); CheckNull(keys.Manager.ActiveScope); } public void WithNoDefaultAnswer_EnterDoesNothing() { MessageBox box = NewBox(); List events = AnswerTests.Record(box); FocusedKeys keys = Entered(box); keys.Manager.ClearFocus(); CheckFalse(keys.Press(KeyCode.MainEnter)); CheckEqual(0, events.Count); } public void WithNoCancelAnswer_EscapeDoesNothing() { MessageBox box = NewBox(); List events = AnswerTests.Record(box); FocusedKeys keys = Entered(box); keys.Press(KeyCode.Escape); CheckEqual(0, events.Count); } public void WithNoKeysBehavior_NeitherDoesAnything() { var box = new MessageBox(TestAnswer.Yes | TestAnswer.No, DialogBehavior.NoKeys) { MessageBoxMaterial = Measurements, DefaultAnswer = TestAnswer.Yes, CancelAnswer = TestAnswer.No, }; var answers = new List(); box.OnAnswered += answers.Add; box.Open(); box.Visible = true; FocusedKeys keys = FocusedKeys.Enter(box); keys.Manager.ClearFocus(); keys.Press(KeyCode.MainEnter); keys.Press(KeyCode.Escape); CheckEqual(0, answers.Count); } public void EnteringTheBox_FocusesTheDefaultButton() { MessageBox box = NewBox(TestAnswer.Yes | TestAnswer.No | TestAnswer.Cancel); box.DefaultAnswer = TestAnswer.No; FocusedKeys keys = Entered(box); CheckEqual(box.ButtonFor(TestAnswer.No), keys.Manager.Focused); } public void EnteringTheBoxWithNoDefault_FocusesTheFirstButton() { MessageBox box = NewBox(TestAnswer.Yes | TestAnswer.No); FocusedKeys keys = Entered(box); CheckEqual(box.ButtonFor(TestAnswer.Yes), keys.Manager.Focused); } public void AFocusedButtonsEnter_BeatsTheDefault() { MessageBox box = NewBox(TestAnswer.Yes | TestAnswer.No); box.DefaultAnswer = TestAnswer.Yes; List events = AnswerTests.Record(box); FocusedKeys keys = Entered(box); keys.Manager.TrySetFocus(box.ButtonFor(TestAnswer.No)); keys.Press(KeyCode.MainEnter); CheckEqual(0, events.Count); keys.Release(KeyCode.MainEnter); CheckEqual(new[] { "No", "closed" }, events.ToArray()); } public void Tab_StaysInsideTheBox() { MessageBox box = NewBox(TestAnswer.Yes | TestAnswer.No); var outside = new Control { Focusable = true }; FocusedKeys keys = Entered(box); keys.Manager.Controls.Add(outside); Control yes = box.ButtonFor(TestAnswer.Yes); Control no = box.ButtonFor(TestAnswer.No); CheckEqual(yes, keys.Manager.Focused); keys.Press(KeyCode.Tab); CheckEqual(no, keys.Manager.Focused); keys.Press(KeyCode.Tab); CheckEqual(yes, keys.Manager.Focused); } public void ShiftTab_StaysInsideTheBox() { MessageBox box = NewBox(TestAnswer.Yes | TestAnswer.No); var outside = new Control { Focusable = true }; FocusedKeys keys = Entered(box); keys.Manager.Controls.Add(outside); keys.Press(KeyCode.ShiftLeft); keys.Press(KeyCode.Tab); CheckEqual(box.ButtonFor(TestAnswer.No), keys.Manager.Focused); } public void ADisabledDefaultButton_DoesNotAnswer() { MessageBox box = NewBox(TestAnswer.Yes | TestAnswer.No); box.DefaultAnswer = TestAnswer.Yes; List events = AnswerTests.Record(box); FocusedKeys keys = Entered(box); box.ButtonFor(TestAnswer.Yes).Enabled = false; keys.Manager.ClearFocus(); keys.Press(KeyCode.MainEnter); CheckEqual(0, events.Count); } } public class AnswerTests : TestFixture { [IgnoreTest] public static List Record(MessageBox box) { var returnMe = new List(); box.OnAnswered += answer => returnMe.Add(answer.ToString()); box.OnClosed += _ => returnMe.Add("closed"); return returnMe; } public void Clicking_RaisesTheAnswerOfThatButtonOnly() { MessageBox box = NewBox(TestAnswer.Yes | TestAnswer.No | TestAnswer.Cancel); List events = Record(box); Click(box.ButtonFor(TestAnswer.No)); CheckEqual(new[] { "No" }, events.ToArray()); } public void ActivatingAFocusedButtonFromTheKeyboard_RaisesItsAnswerOnly() { MessageBox box = NewBox(TestAnswer.Yes | TestAnswer.No | TestAnswer.Cancel); List events = Record(box); var keys = new FocusedKeys(box, box.ButtonFor(TestAnswer.No)); keys.Press(KeyCode.Space); CheckEqual(0, events.Count); keys.Release(KeyCode.Space); CheckEqual(new[] { "No" }, events.ToArray()); } public void ActivatingAnOpenBoxsButtonFromTheKeyboard_AnswersThenCloses() { MessageBox box = NewBox(); List events = Record(box); box.Open(); var keys = new FocusedKeys(box, box.ButtonFor(TestAnswer.Yes)); keys.Press(KeyCode.MainEnter); keys.Release(KeyCode.MainEnter); CheckEqual(new[] { "Yes", "closed" }, events.ToArray()); CheckFalse(box.IsOpen); } public void Clicking_AnOpenBox_AnswersThenCloses() { MessageBox box = NewBox(); List events = Record(box); box.Open(); Click(box.ButtonFor(TestAnswer.Yes)); CheckEqual(new[] { "Yes", "closed" }, events.ToArray()); CheckFalse(box.IsOpen); } public void TheTopBitOfAWideEnum_IsAnsweredAsItself() { var box = new MessageBox(WideAnswer.Low | WideAnswer.High, DialogBehavior.Default); WideAnswer? answered = null; box.OnAnswered += answer => answered = answer; Click(box.ButtonFor(WideAnswer.High)); CheckEqual(WideAnswer.High, answered); } public void TheSignBitOfASignedEnum_IsAnsweredAsItself() { var box = new MessageBox(SignedAnswer.Low | SignedAnswer.Sign, DialogBehavior.Default); SignedAnswer? answered = null; box.OnAnswered += answer => answered = answer; Click(box.ButtonFor(SignedAnswer.Sign)); CheckEqual(SignedAnswer.Sign, answered); } } } }