#region CPL License /* Nuclex Framework Copyright (C) 2002-2009 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 NMock; using Nuclex.UserInterface.Input; using Nuclex.Input; using Is = NUnit.Framework.Is; namespace Nuclex.UserInterface { /// Unit Test for the Screen class [TestFixture] internal class ScreenTest { #region class DelegatingControl /// Control that delegates input to another input receiver public class DelegatingControl : Controls.Control { /// Initializes a new input delegating control /// Receiver to which the input is delegated public DelegatingControl(IInputReceiver receiver) { this.receiver = receiver; } /// Called when a command was sent to the control /// Command the control should perform /// Whether the command has been processed by the control protected override bool OnCommand(Command command) { this.receiver.InjectCommand(command); return true; } /// Called when the mouse position is updated /// X coordinate of the mouse cursor on the GUI /// Y coordinate of the mouse cursor on the GUI protected override void OnMouseMoved(float x, float y) { this.receiver.InjectMouseMove(x, y); } /// Called when a mouse button has been pressed down /// Index of the button that has been pressed protected override void OnMousePressed(MouseButtons button) { this.receiver.InjectMousePress(button); } /// Called when a mouse button has been released again /// Index of the button that has been released protected override void OnMouseReleased(MouseButtons button) { this.receiver.InjectMouseRelease(button); } /// 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.receiver.InjectMouseWheel(ticks); } /// Called when a key on the keyboard has been pressed down /// Code of the key that was pressed protected override bool OnKeyPressed(Keys keyCode) { this.receiver.InjectKeyPress(keyCode); return true; } /// 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.receiver.InjectKeyRelease(keyCode); } /// Input receiver all received input is delegated to private IInputReceiver receiver; } #endregion // class DelegatingControl #region class GamePadTestControl /// Control used to test game pad notifications private class GamePadTestControl : Controls.Control { /// Called when a button on the game pad 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 true; } /// Called when a button on the game pad has been released /// Button that has been released protected override void OnButtonReleased(Buttons button) { --this.HeldButtonCount; base.OnButtonReleased(button); } /// Number of game pad buttons being held down public int HeldButtonCount; } #endregion // class GamePadTestControl #region class KeyboardTestControl /// Control used to test keyboard notifications private class KeyboardTestControl : Controls.Control { /// 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); return true; } /// 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); } /// Number of keys being held down public int HeldKeyCount; } #endregion // class KeyboardTestControl #region class MouseTestControl /// Control used to test mouse notifications private class MouseTestControl : Controls.Control { /// 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) { return true; } /// Called when a mouse button has been pressed down /// Index of the button that has been pressed /// Whether the control has processed the mouse press protected override void OnMousePressed(MouseButtons button) { this.HeldMouseButtons |= button; base.OnMousePressed(button); } /// Called when a mouse button has been released again /// Index of the button that has been released protected override void OnMouseReleased(MouseButtons button) { this.HeldMouseButtons &= ~button; base.OnMouseReleased(button); } /// 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.MouseWheelTicks += ticks; base.OnMouseWheel(ticks); } /// Mouse buttons being held down public MouseButtons HeldMouseButtons; /// Ticks the mouse wheel has been rotated by public float MouseWheelTicks; } #endregion // class GamePadTestControl #region class CommandTestControl /// Control for testing command routing private class CommandTestControl : Controls.Control { /// Called when an input command was sent to the control /// Input command that has been triggered /// Whether the command has been processed by the control protected override bool OnCommand(Command command) { this.LastCommand = command; return true; } public Command LastCommand; } #endregion // class CommandTestControl #region interface IFocusChangeSubscriber /// /// Interface for a subscriber to a Control's FocusChanged event /// public interface IFocusChangeSubscriber { /// Called when the focused control has changed /// Screen that is reporting the focus change /// Contains the control that is now focused void FocusChanged(object sender, Controls.ControlEventArgs arguments); } #endregion // interface IFocusChangeSubscriber /// Initialization routine executed before each test is run [SetUp] public void Setup() { this.mockery = new MockFactory(); } /// Called after each test has run [TearDown] public void Teardown() { if(this.mockery != null) { this.mockery.Dispose(); this.mockery = null; } } /// Tests the default constructor of the screen class [Test] public void TestDefaultConstructor() { Screen myScreen = new Screen(); Assert.That(myScreen.Width, Is.EqualTo(0.0f)); Assert.That(myScreen.Height, Is.EqualTo(0.0f)); } /// Tests the full featured constructor of the screen class [Test] public void TestFullConstructor() { Screen myScreen = new Screen(12.34f, 56.78f); Assert.That(myScreen.Width, Is.EqualTo(12.34f)); Assert.That(myScreen.Height, Is.EqualTo(56.78f)); } /// /// Verifies that an action input is processed by the screen /// [Test] public void TestCommandProcessing() { Screen testScreen = new Screen(); testScreen.InjectCommand(Command.Accept); testScreen.InjectCommand(Command.Cancel); // No exception means success } /// /// Tests whether the Gui reference is properly propagated to all controls /// and their children in a control tree. /// [Test] public void TestScreenPropagationOnInsertion() { Screen myScreen = new Screen(); Controls.Control child = new Controls.Control(); myScreen.Desktop.Children.Add(child); Assert.AreSame(myScreen, child.Screen); } /// /// Verifies that button presses are propagated down the control tree /// [Test] public void TestInjectButtonPress() { Screen screen = new Screen(); GamePadTestControl control = new GamePadTestControl(); screen.Desktop.Children.Add(control); screen.InjectButtonPress(Buttons.A); Assert.AreEqual(1, control.HeldButtonCount); } /// /// Verifies that button press notifications are routed to the activated control /// instead of searching for a control to handle the press /// [Test] public void TestButtonPressWithActivatedControl() { Screen screen = new Screen(100.0f, 100.0f); GamePadTestControl child1 = new GamePadTestControl(); GamePadTestControl child2 = new GamePadTestControl(); child2.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f); screen.Desktop.Children.Add(child1); screen.Desktop.Children.Add(child2); // Click on child 2 screen.InjectMouseMove(50.0f, 50.0f); screen.InjectMousePress(MouseButtons.Left); // Now child 2 should be receiving the input instead of child 1 screen.InjectButtonPress(Buttons.A); Assert.AreEqual(0, child1.HeldButtonCount); Assert.AreEqual(1, child2.HeldButtonCount); screen.InjectButtonRelease(Buttons.A); Assert.AreEqual(0, child1.HeldButtonCount); Assert.AreEqual(0, child2.HeldButtonCount); } /// /// Verifies that button press notifications are sent to the focused control /// first when looking the a control that handles the notification /// [Test] public void TestButtonPressWithFocusedControl() { Screen screen = new Screen(100.0f, 100.0f); GamePadTestControl child1 = new GamePadTestControl(); GamePadTestControl child2 = new GamePadTestControl(); screen.Desktop.Children.Add(child1); screen.Desktop.Children.Add(child2); screen.FocusedControl = child2; screen.InjectButtonPress(Buttons.A); Assert.AreEqual(0, child1.HeldButtonCount); Assert.AreEqual(1, child2.HeldButtonCount); screen.InjectButtonRelease(Buttons.A); Assert.AreEqual(0, child1.HeldButtonCount); Assert.AreEqual(0, child2.HeldButtonCount); } /// /// Verifies that key presses are propagated down the control tree /// [Test] public void TestInjectKeyPress() { Screen screen = new Screen(); KeyboardTestControl control = new KeyboardTestControl(); screen.Desktop.Children.Add(control); screen.InjectKeyPress(Keys.A); Assert.AreEqual(1, control.HeldKeyCount); } /// /// Verifies that key press notifications are routed to the activated control /// instead of searching for a control to handle the press /// [Test] public void TestKeyPressWithActivatedControl() { Screen screen = new Screen(100.0f, 100.0f); KeyboardTestControl child1 = new KeyboardTestControl(); KeyboardTestControl child2 = new KeyboardTestControl(); child2.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f); screen.Desktop.Children.Add(child1); screen.Desktop.Children.Add(child2); // Click on child 2 screen.InjectMouseMove(50.0f, 50.0f); screen.InjectMousePress(MouseButtons.Left); // Now child 2 should be receiving the input instead of child 1 screen.InjectKeyPress(Keys.A); Assert.AreEqual(0, child1.HeldKeyCount); Assert.AreEqual(1, child2.HeldKeyCount); screen.InjectKeyRelease(Keys.A); Assert.AreEqual(0, child1.HeldKeyCount); Assert.AreEqual(0, child2.HeldKeyCount); } /// /// Verifies that key press notifications are sent to the focused control /// first when looking the a control that handles the notification /// [Test] public void TestKeyPressWithFocusedControl() { Screen screen = new Screen(100.0f, 100.0f); KeyboardTestControl child1 = new KeyboardTestControl(); KeyboardTestControl child2 = new KeyboardTestControl(); screen.Desktop.Children.Add(child1); screen.Desktop.Children.Add(child2); screen.FocusedControl = child2; screen.InjectKeyPress(Keys.A); Assert.AreEqual(0, child1.HeldKeyCount); Assert.AreEqual(1, child2.HeldKeyCount); screen.InjectKeyRelease(Keys.A); Assert.AreEqual(0, child1.HeldKeyCount); Assert.AreEqual(0, child2.HeldKeyCount); } /// /// Verifies that mouse pressed are routed to the activated control /// [Test] public void TestMousePressWithActivatedControl() { Screen screen = new Screen(100.0f, 100.0f); MouseTestControl child = new MouseTestControl(); child.Bounds = new UniRectangle(55.0f, 10.0f, 35.0f, 80.0f); screen.Desktop.Children.Add(child); screen.InjectKeyPress(Keys.A); screen.InjectMousePress(MouseButtons.Left); Assert.AreEqual(MouseButtons.Left, child.HeldMouseButtons); } /// /// Verifies that the FocusChanged event is triggered when the control /// in focus changes /// [Test] public void TestFocusChangeEvent() { Screen screen = new Screen(100.0f, 100.0f); Mock mockedSubscriber = mockSubscriber(screen); Controls.Control child1 = new Controls.Control(); Controls.Control child2 = new Controls.Control(); screen.Desktop.Children.Add(child1); screen.Desktop.Children.Add(child2); mockedSubscriber.Expects.One.Method(m => m.FocusChanged(null, null)).WithAnyArguments(); screen.FocusedControl = child1; mockedSubscriber.Expects.One.Method(m => m.FocusChanged(null, null)).WithAnyArguments(); screen.FocusedControl = child2; this.mockery.VerifyAllExpectationsHaveBeenMet(); } /// /// Verifies that mouse wheel rotations are propagated down the control tree /// [Test] public void TestMouseWheel() { Screen screen = new Screen(); MouseTestControl control = new MouseTestControl(); control.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f); screen.Desktop.Children.Add(control); screen.InjectMouseMove(50.0f, 50.0f); screen.InjectMouseWheel(12.34f); Assert.AreEqual(12.34f, control.MouseWheelTicks); } /// /// Verifies that mouse wheel rotations are sent to the activated control first /// [Test] public void TestMouseWheelWithActivatedControl() { Screen screen = new Screen(100.0f, 100.0f); MouseTestControl control = new MouseTestControl(); control.Bounds = new UniRectangle(10.0f, 10.0f, 80.0f, 80.0f); screen.Desktop.Children.Add(control); screen.InjectKeyPress(Keys.A); screen.InjectMouseWheel(12.34f); Assert.AreEqual(12.34f, control.MouseWheelTicks); } /// /// Verifies that entered characters are sent to the focused control /// [Test] public void TestInjectCharacter() { Screen screen = new Screen(100.0f, 100.0f); Controls.Desktop.InputControl control = new Controls.Desktop.InputControl(); screen.Desktop.Children.Add(control); screen.InjectCharacter('a'); Assert.AreEqual(string.Empty, control.Text); screen.FocusedControl = control; screen.InjectCharacter('a'); Assert.AreEqual("a", control.Text); } /// Tests whether focus can be changed using the keyboard [Test] public void TestFocusSwitching() { Screen screen = new Screen(100.0f, 100.0f); Controls.Desktop.ButtonControl center = new Controls.Desktop.ButtonControl(); center.Bounds = new UniRectangle(40, 40, 20, 20); Controls.Desktop.ButtonControl left = new Controls.Desktop.ButtonControl(); left.Bounds = new UniRectangle(10, 51, 20, 20); Controls.Desktop.ButtonControl right = new Controls.Desktop.ButtonControl(); right.Bounds = new UniRectangle(70, 29, 20, 20); Controls.Desktop.ButtonControl up = new Controls.Desktop.ButtonControl(); up.Bounds = new UniRectangle(29, 10, 20, 20); Controls.Desktop.ButtonControl down = new Controls.Desktop.ButtonControl(); down.Bounds = new UniRectangle(51, 70, 20, 20); screen.Desktop.Children.Add(center); screen.Desktop.Children.Add(left); screen.Desktop.Children.Add(right); screen.Desktop.Children.Add(up); screen.Desktop.Children.Add(down); screen.FocusedControl = center; screen.InjectKeyPress(Keys.Down); Assert.AreSame(down, screen.FocusedControl); screen.FocusedControl = center; screen.InjectKeyPress(Keys.Up); Assert.AreSame(up, screen.FocusedControl); screen.FocusedControl = center; screen.InjectKeyPress(Keys.Left); Assert.AreSame(left, screen.FocusedControl); screen.FocusedControl = center; screen.InjectKeyPress(Keys.Right); Assert.AreSame(right, screen.FocusedControl); } /// /// Verifies that the screen skips an unfocusable control and jumps to the /// next focusable control. /// [Test] public void TestFocusSwitchingWithUnfocusableControl() { Screen screen = new Screen(100.0f, 100.0f); Controls.Desktop.ButtonControl one = new Controls.Desktop.ButtonControl(); one.Bounds = new UniRectangle(40, 10, 20, 20); Controls.Control two = new Controls.Control(); two.Bounds = new UniRectangle(40, 40, 20, 20); Controls.Desktop.ButtonControl three = new Controls.Desktop.ButtonControl(); three.Bounds = new UniRectangle(40, 70, 20, 20); screen.Desktop.Children.Add(one); screen.Desktop.Children.Add(two); screen.Desktop.Children.Add(three); screen.FocusedControl = one; screen.InjectKeyPress(Keys.Down); screen.InjectKeyRelease(Keys.Down); Assert.AreSame(three, screen.FocusedControl); } /// /// Verifies that if the focused control handles a directional command, no /// focus switching will occur /// [Test] public void TestNoFocusChangeOnHandledDirectionalCommand() { Screen screen = new Screen(100.0f, 100.0f); Mock mockedReceiver = mockReceiver(screen); DelegatingControl one = new DelegatingControl(mockedReceiver.MockObject); one.Bounds = new UniRectangle(40, 10, 20, 20); Controls.Desktop.ButtonControl two = new Controls.Desktop.ButtonControl(); two.Bounds = new UniRectangle(40, 40, 20, 20); screen.Desktop.Children.Add(one); screen.Desktop.Children.Add(two); mockedReceiver.Expects.One.MethodWith(m => m.InjectCommand(Command.Down)); screen.FocusedControl = one; screen.InjectCommand(Command.Down); Assert.AreSame(one, screen.FocusedControl); this.mockery.VerifyAllExpectationsHaveBeenMet(); } /// /// Verifies that the screen can handle a focus switch request without any /// focused control /// [Test] public void TestFocusSwitchingWithoutFocusedControl() { Screen screen = new Screen(100.0f, 100.0f); screen.InjectCommand(Command.Down); // No exception means success } /// /// Verifies that the screen can handle a focus switch when the control /// currently assigned as the focused control has been disconnected from the tree /// [Test] public void TestFocusSwitchingWithDisconnectedControl() { Screen screen = new Screen(100.0f, 100.0f); screen.FocusedControl = new Controls.Control(); screen.InjectCommand(Command.Down); // No exception means success } /// /// Verifies that classic focus switching is still supported /// [Test] public void TestClassicFocusSwitching() { Screen screen = new Screen(100.0f, 100.0f); screen.InjectCommand(Command.SelectPrevious); screen.InjectCommand(Command.SelectNext); // TODO: Implement classic focus switching and do some real testing here } /// /// Verifies that classic focus switching is still supported /// [Test] public void TestAcceptAndCancelFromKeyboard() { Screen screen = new Screen(100.0f, 100.0f); CommandTestControl test = new CommandTestControl(); screen.Desktop.Children.Add(test); screen.FocusedControl = test; screen.InjectKeyPress(Keys.Escape); Assert.AreEqual(Command.Cancel, test.LastCommand); screen.InjectKeyPress(Keys.Enter); Assert.AreEqual(Command.Accept, test.LastCommand); } /// Mocks a receiver for the input processing of a control /// Screen to mock an input receiver on /// The mocked input receiver private Mock mockReceiver(Screen screen) { Mock mockedReceiver = this.mockery.CreateMock(); DelegatingControl delegatingControl = new DelegatingControl(mockedReceiver.MockObject); screen.Desktop.Children.Add(delegatingControl); screen.FocusedControl = delegatingControl; return mockedReceiver; } /// Mocks a subscriber to the screen's events /// Screen to mock an event subcriber to /// The mocked event subscriber private Mock mockSubscriber(Screen screen) { Mock mockedSubscriber = this.mockery.CreateMock(); screen.FocusChanged += new EventHandler( mockedSubscriber.MockObject.FocusChanged ); return mockedSubscriber; } /// Mock object factory private MockFactory mockery; } } // namespace Nuclex.UserInterface #endif // UNITTEST