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);
}
}
}
}