#region CPL License /* Nuclex Framework Copyright (C) 2002-2010 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion #if UNITTEST using System; using Microsoft.Xna.Framework.Input; using NUnit.Framework; using Nuclex.Input; using Nuclex.Support; using Nuclex.UserInterface.Input; namespace Nuclex.UserInterface.Controls { /// Unit Test for the control class [TestFixture] internal class ControlTest { #region class MouseOverTestControl /// Control used for testing the mouse over notifications private class MouseOverTestControl : Control { /// /// Called when the mouse has entered the control and is now hovering over it /// protected override void OnMouseEntered() { this.MouseOver = true; base.OnMouseEntered(); // not needed, for test coverage ;-) } /// /// Called when the mouse has left the control and is no longer hovering over it /// protected override void OnMouseLeft() { this.MouseOver = false; base.OnMouseLeft(); // not needed, for test coverage ;-) } /// Whether the mouse is currently hovering over the control public bool MouseOver; } #endregion // class MouseOverTestControl #region class MouseWheelTestControl /// Control used for testing the mouse wheel notification private class MouseWheelTestControl : Control { /// Called when the mouse wheel has been rotated /// Number of ticks that the mouse wheel has been rotated protected override void OnMouseWheel(float ticks) { this.Ticks += ticks; base.OnMouseWheel(ticks); // not needed; only for test coverage ;-) } /// Number of ticks the mouse wheel has been moved public float Ticks; } #endregion // class MouseWheelTestControl #region class KeyboardTestControl /// Control used for testing the keyboard notification private class KeyboardTestControl : Control { /// Initializes a new keyboard test control /// /// Whether the control will claim responsibility for input routed to it /// public KeyboardTestControl(bool responsible) { this.responsible = responsible; } /// Called when a key on the keyboard has been pressed down /// Code of the key that was pressed /// /// True if the key press was handled by the control, otherwise false. /// protected override bool OnKeyPressed(Keys keyCode) { ++this.HeldKeyCount; base.OnKeyPressed(keyCode); // not needed; only for test coverage ;-) return this.responsible; } /// Called when a key on the keyboard has been released again /// Code of the key that was released protected override void OnKeyReleased(Keys keyCode) { --this.HeldKeyCount; base.OnKeyReleased(keyCode); // not needed; only for test coverage ;-) } /// Number of keys being held down public int HeldKeyCount; /// /// Whether the control claims responsibility for input routed to it /// private bool responsible; } #endregion // class KeyboardTestControl #region class GamePadTestControl /// Control used for testing the keyboard notification private class GamePadTestControl : Control { /// Initializes a new keyboard test control /// /// Whether the control will claim responsibility for input routed to it /// public GamePadTestControl(bool responsible) { this.responsible = responsible; } /// Called when a button on the gamepad has been pressed /// Button that has been pressed /// /// True if the button press was handled by the control, otherwise false. /// protected override bool OnButtonPressed(Buttons button) { ++this.HeldButtonCount; base.OnButtonPressed(button); return this.responsible; } /// Called when a button on the gamepad has been released /// Button that has been released protected override void OnButtonReleased(Buttons button) { --this.HeldButtonCount; base.OnButtonReleased(button); } /// Number of keys being held down public int HeldButtonCount; /// /// Whether the control claims responsibility for input routed to it /// private bool responsible; } #endregion // class GamePadTestControl /// /// Tests whether the control detects an id collision with one of its siblings /// when the id is assigned to an already used string. /// [Test] public void TestNameCollisionOnIdAssignment() { Control parent = new Control(); Control child1 = new Control(); Control child2 = new Control(); parent.Children.Add(child1); parent.Children.Add(child2); child1.Name = "DuplicateName"; Assert.Throws( delegate() { child2.Name = "DuplicateName"; } ); } /// /// Tests whether the control detects an id collision with one of its siblings /// when a sibling is added that has the same name. /// [Test] public void TestNameCollisionOnInsertion() { Control parent = new Control(); Control child1 = new Control(); Control child2 = new Control(); child1.Name = "DuplicateName"; child2.Name = "DuplicateName"; parent.Children.Add(child1); Assert.Throws( delegate() { parent.Children.Add(child2); } ); } /// Verifies that the control rejects unsupported commands [Test] public void TestUnsupportedCommands() { Control control = new Control(); // These will never be acknowledged Assert.IsFalse(control.ProcessCommand(Command.SelectPrevious)); Assert.IsFalse(control.ProcessCommand(Command.SelectNext)); } /// Verifies that the control accepts supported commands [Test] public void TestSupportedCommands() { Control control = new Control(); // False because the control doesn't acknowledge them Assert.IsFalse(control.ProcessCommand(Command.Up)); Assert.IsFalse(control.ProcessCommand(Command.Down)); Assert.IsFalse(control.ProcessCommand(Command.Left)); Assert.IsFalse(control.ProcessCommand(Command.Right)); Assert.IsFalse(control.ProcessCommand(Command.Accept)); Assert.IsFalse(control.ProcessCommand(Command.Cancel)); } /// /// Validates that the control can correctly resolve its unified coordinates /// into absolute screen coordinates if parented to a screen. /// [Test] public void TestAbsoluteCoordinateTransformation() { // Create a test control that occupies 80 percent of the space in its // parent using unified coordinates Control myControl = new Control(); myControl.Bounds.Left = new UniScalar(0.1f, 0); myControl.Bounds.Top = new UniScalar(0.1f, 0); myControl.Bounds.Right = new UniScalar(0.9f, 0); myControl.Bounds.Bottom = new UniScalar(0.9f, 0); // Place the test control on a screen sized 1000 x 1000 pixels Screen myScreen = new Screen(1000.0f, 1000.0f); myScreen.Desktop.Children.Add(myControl); // Verify that the test control's absolute coordinates reflect its size // given in unified coordinates RectangleF absoluteBoundaries = myControl.GetAbsoluteBounds(); assertAlmostEqual(100.0f, absoluteBoundaries.Left); assertAlmostEqual(100.0f, absoluteBoundaries.Top); assertAlmostEqual(900.0f, absoluteBoundaries.Right); assertAlmostEqual(900.0f, absoluteBoundaries.Bottom); // Now change the size of the desktop to only one fourth of the screen myScreen.Desktop.Bounds.Location.X.Fraction = 0.5f; myScreen.Desktop.Bounds.Location.Y.Fraction = 0.5f; myScreen.Desktop.Bounds.Size.X.Fraction = 0.5f; myScreen.Desktop.Bounds.Size.Y.Fraction = 0.5f; // Verify that the desktop size change is reflected in the absolute // coordinates that control's GetAbsoluteBounds() method hands out absoluteBoundaries = myControl.GetAbsoluteBounds(); assertAlmostEqual(550.0f, absoluteBoundaries.Left); assertAlmostEqual(550.0f, absoluteBoundaries.Top); assertAlmostEqual(950.0f, absoluteBoundaries.Right); assertAlmostEqual(950.0f, absoluteBoundaries.Bottom); } /// /// Verifies that the control throws an exception if it is asked to provide its /// absolute position within being connected to a screen. /// [Test] public void TestThrowsOnGetAbsolutePositionWithoutScreen() { Control control = new Control(); Assert.Throws( delegate() { control.GetAbsoluteBounds(); } ); } /// /// Tests whether a control can be brought to the front of the drawing hierarchy /// [Test] public void TestBringToFront() { // Create this: // // Root // |- Child 1 // |- Child 2 // |- Child 2 Child 1 // |- Child 2 Child 2 Control root = new Control(); Control child1 = new Control(); Control child2 = new Control(); Control child2Child1 = new Control(); Control child2Child2 = new Control(); root.Children.Add(child1); root.Children.Add(child2); child2.Children.Add(child2Child1); child2.Children.Add(child2Child2); // The second child of each control should not be on top Assert.AreNotSame(child2, root.Children[0]); Assert.AreNotSame(child2Child2, child2.Children[0]); // Bring the control to the top. This should recursively move its parents // to the top of the siblings so the control ends up in the foreground. child2Child2.BringToFront(); // Make sure the control and its parent are the first one in each list Assert.AreSame(child2, root.Children[0]); Assert.AreSame(child2Child2, child2.Children[0]); } /// /// Verifies that mouse presses outside of the control's area can be handled /// [Test] public void TestInitialMousePressOutsideOfControl() { Control control = new Control(); control.ProcessMousePress(MouseButtons.Left); control.ProcessMouseRelease(MouseButtons.Left); } /// /// Ensures that the control can handle mouse over notifications if its notification /// is not overridden and it has no children /// [Test] public void TestMouseOver() { MouseOverTestControl control = new MouseOverTestControl(); control.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f); Assert.IsFalse(control.MouseOver); control.ProcessMouseMove(100.0f, 100.0f, 20.0f, 20.0f); Assert.IsTrue(control.MouseOver); control.ProcessMouseMove(100.0f, 100.0f, -1.0f, -1.0f); Assert.IsFalse(control.MouseOver); } /// /// Ensures that the control passes on mouse over notifications to its children /// [Test] public void TestMouseOverChildren() { Control parent = new Control(); parent.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f); MouseOverTestControl child1 = new MouseOverTestControl(); child1.Bounds = new UniRectangle(10.0f, 10.0f, 25.0f, 60.0f); MouseOverTestControl child2 = new MouseOverTestControl(); child2.Bounds = new UniRectangle(45.0f, 10.0f, 25.0f, 60.0f); parent.Children.Add(child1); parent.Children.Add(child2); Assert.IsFalse(child1.MouseOver); Assert.IsFalse(child2.MouseOver); parent.ProcessMouseMove(100.0f, 100.0f, 20.0f, 30.0f); Assert.IsTrue(child1.MouseOver); Assert.IsFalse(child2.MouseOver); parent.ProcessMouseMove(100.0f, 100.0f, 60.0f, 30.0f); Assert.IsFalse(child1.MouseOver); Assert.IsTrue(child2.MouseOver); } /// /// Ensures that the control passes mouse movement notifications to the activated /// control first. /// [Test] public void TestMouseOverWithActivatedControl() { Control parent = new Control(); parent.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f); MouseOverTestControl child = new MouseOverTestControl(); child.Bounds = new UniRectangle(10.0f, 10.0f, 25.0f, 60.0f); parent.Children.Add(child); parent.ProcessMouseMove(100.0f, 100.0f, 20.0f, 30.0f); Assert.IsTrue(child.MouseOver); parent.ProcessMousePress(MouseButtons.Left); parent.ProcessMouseMove(100.0f, 100.0f, -1.0f, -1.0f); Assert.IsFalse(child.MouseOver); } /// /// Ensures that a control with no overridden methods mouse presses are ignored /// [Test] public void TestIgnoreMousePress() { Control control = new Control(); control.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f); control.ProcessMouseMove(100.0f, 100.0f, 20.0f, 20.0f); control.ProcessMousePress(MouseButtons.Left); control.ProcessMouseRelease(MouseButtons.Left); } /// /// Verifies that order-affecting controls are reordered when the user clicks /// on a control /// [Test] public void TestReorderControlsOnMousePress() { Control parent = new Control(); parent.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f); Controls.Desktop.WindowControl child1 = new Controls.Desktop.WindowControl(); child1.Bounds = new UniRectangle(10.0f, 10.0f, 25.0f, 60.0f); Controls.Desktop.WindowControl child2 = new Controls.Desktop.WindowControl(); child2.Bounds = new UniRectangle(45.0f, 10.0f, 25.0f, 60.0f); parent.Children.Add(child1); parent.Children.Add(child2); Assert.AreSame(child1, parent.Children[0]); Assert.AreSame(child2, parent.Children[1]); parent.ProcessMouseMove(100.0f, 100.0f, 60.0f, 30.0f); parent.ProcessMousePress(MouseButtons.Left); Assert.AreSame(child2, parent.Children[0]); Assert.AreSame(child1, parent.Children[1]); } /// /// Verifies that mouse wheel turns are delivered to the activated control /// [Test] public void TestMouseWheelWithActivatedControl() { Control parent = new Control(); MouseWheelTestControl child = new MouseWheelTestControl(); child.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f); parent.Children.Add(child); parent.ProcessMouseMove(100.0f, 100.0f, 50.0f, 50.0f); parent.ProcessMousePress(MouseButtons.Left); parent.ProcessMouseMove(100.0f, 100.0f, -1.0f, -1.0f); Assert.AreEqual(0.0f, child.Ticks); parent.ProcessMouseWheel(12.34f); Assert.AreEqual(12.34f, child.Ticks); } /// /// Verifies that mouse wheel turns are delivered to the control the mouse /// is over when no control is activated /// [Test] public void TestMouseWheelWithMouseOverControl() { Control parent = new Control(); MouseWheelTestControl child = new MouseWheelTestControl(); child.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f); parent.Children.Add(child); parent.ProcessMouseMove(100.0f, 100.0f, 50.0f, 50.0f); Assert.AreEqual(0.0f, child.Ticks); parent.ProcessMouseWheel(12.34f); Assert.AreEqual(12.34f, child.Ticks); } /// /// Verifies that keyboard messages are routed to the activated control /// [Test] public void TestKeyPressWithActivatedControl() { Control parent = new Control(); parent.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f); KeyboardTestControl child1 = new KeyboardTestControl(true); KeyboardTestControl child2 = new KeyboardTestControl(true); child2.Bounds = new UniRectangle(10.0f, 10.0f, 25.0f, 60.0f); parent.Children.Add(child1); parent.Children.Add(child2); parent.ProcessMouseMove(100.0f, 100.0f, 20.0f, 30.0f); parent.ProcessMousePress(MouseButtons.Left); Assert.AreEqual(0, child1.HeldKeyCount); Assert.AreEqual(0, child2.HeldKeyCount); parent.ProcessKeyPress(Keys.A, false); Assert.AreEqual(0, child1.HeldKeyCount); Assert.AreEqual(1, child2.HeldKeyCount); // Because child 1 was activated parent.ProcessKeyRelease(Keys.A); Assert.AreEqual(0, child1.HeldKeyCount); Assert.AreEqual(0, child2.HeldKeyCount); } /// /// Verifies that keyboard messages are only sent to the foreground window /// [Test] public void TestKeyPressOnFocusAffectingControl() { Control parent = new Control(); Controls.Desktop.WindowControl child1 = new Controls.Desktop.WindowControl(); Controls.Desktop.WindowControl child2 = new Controls.Desktop.WindowControl(); KeyboardTestControl child3 = new KeyboardTestControl(true); parent.Children.Add(child1); parent.Children.Add(child2); parent.Children.Add(child3); KeyboardTestControl child1child1 = new KeyboardTestControl(false); KeyboardTestControl child1child2 = new KeyboardTestControl(false); KeyboardTestControl child2child1 = new KeyboardTestControl(false); child1.Children.Add(child1child1); child1.Children.Add(child1child2); child2.Children.Add(child2child1); parent.ProcessKeyPress(Keys.A, false); Assert.AreEqual(1, child1child1.HeldKeyCount); Assert.AreEqual(1, child1child2.HeldKeyCount); // because child 1 returned false Assert.AreEqual(0, child2child1.HeldKeyCount); // because its parent affects focus Assert.AreEqual(1, child3.HeldKeyCount); // because it doesn't affect focus } /// /// Verifies that game pad messages are routed to the activated control /// [Test] public void TestButtonPressWithActivatedControl() { Control parent = new Control(); parent.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f); GamePadTestControl child1 = new GamePadTestControl(true); GamePadTestControl child2 = new GamePadTestControl(true); child2.Bounds = new UniRectangle(10.0f, 10.0f, 25.0f, 60.0f); parent.Children.Add(child1); parent.Children.Add(child2); parent.ProcessMouseMove(100.0f, 100.0f, 20.0f, 30.0f); parent.ProcessMousePress(MouseButtons.Left); Assert.AreEqual(0, child1.HeldButtonCount); Assert.AreEqual(0, child2.HeldButtonCount); parent.ProcessButtonPress(Buttons.A); Assert.AreEqual(0, child1.HeldButtonCount); Assert.AreEqual(1, child2.HeldButtonCount); // Because child 1 was activated parent.ProcessButtonRelease(Buttons.A); Assert.AreEqual(0, child1.HeldButtonCount); Assert.AreEqual(0, child2.HeldButtonCount); } /// /// Verifies that game pad messages are only sent to the foreground window /// [Test] public void TestButtonPressOnFocusAffectingControl() { Control parent = new Control(); Controls.Desktop.WindowControl child1 = new Controls.Desktop.WindowControl(); Controls.Desktop.WindowControl child2 = new Controls.Desktop.WindowControl(); GamePadTestControl child3 = new GamePadTestControl(true); parent.Children.Add(child1); parent.Children.Add(child2); parent.Children.Add(child3); GamePadTestControl child1child1 = new GamePadTestControl(false); GamePadTestControl child1child2 = new GamePadTestControl(false); GamePadTestControl child2child1 = new GamePadTestControl(false); child1.Children.Add(child1child1); child1.Children.Add(child1child2); child2.Children.Add(child2child1); parent.ProcessButtonPress(Buttons.A); Assert.AreEqual(1, child1child1.HeldButtonCount); Assert.AreEqual(1, child1child2.HeldButtonCount); // because child 1 returned false Assert.AreEqual(0, child2child1.HeldButtonCount); // because its parent affects focus Assert.AreEqual(1, child3.HeldButtonCount); // because it doesn't affect focus } /// Asserts that two floating point values are almost equal /// Expected value /// Actual value private void assertAlmostEqual(float expected, float actual) { if(!FloatHelper.AreAlmostEqual(expected, actual, 1)) { Assert.AreEqual(expected, actual); } } } } // namespace Nuclex.UserInterface.Controls #endif // UNITTEST