using System; using UnitTestSharp; namespace Clave.UnitTests { public class KeyTrackerTests : TestFixture { public static KeyState Keys(params KeyCode[] keys) { return new KeyState(keys); } public static KeySnapshot Snapshot(KeyState held = default, KeyState pressed = default, KeyState released = default, KeyState repeated = default) { return new KeySnapshot(held, pressed, released, repeated); } public static KeyboardSnapshot Down(KeyTracker tracker, KeyCode physical, KeyCode layout = KeyCode.None) { return tracker.KeyDown(physical, layout, isRepeat: false); } public static KeyboardSnapshot Repeat(KeyTracker tracker, KeyCode physical, KeyCode layout = KeyCode.None) { return tracker.KeyDown(physical, layout, isRepeat: true); } public class KeyDownTests : TestFixture { public void NewKey_IsPressedAndHeld() { KeyboardSnapshot keyboard = Down(new KeyTracker(), KeyCode.A); CheckEqual(Snapshot(held: Keys(KeyCode.A), pressed: Keys(KeyCode.A)), keyboard.Physical); } public void OtherHeldKeys_StayHeldWithoutBeingPressed() { var tracker = new KeyTracker(); Down(tracker, KeyCode.ControlLeft); KeyboardSnapshot keyboard = Down(tracker, KeyCode.Z); CheckEqual(Snapshot(held: Keys(KeyCode.ControlLeft, KeyCode.Z), pressed: Keys(KeyCode.Z)), keyboard.Physical); } public void Repeat_IsRepeatedAndHeldButNotPressed() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A); KeyboardSnapshot keyboard = Repeat(tracker, KeyCode.A); CheckEqual(Snapshot(held: Keys(KeyCode.A), repeated: Keys(KeyCode.A)), keyboard.Physical); } public void RepeatOfAKeyNotSeenGoingDown_IsRepeatedAndNowHeld() { KeyboardSnapshot keyboard = Repeat(new KeyTracker(), KeyCode.A); CheckEqual(Snapshot(held: Keys(KeyCode.A), repeated: Keys(KeyCode.A)), keyboard.Physical); } public void DownOfAKeyAlreadyHeld_IsAFreshPress() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A); KeyboardSnapshot keyboard = Down(tracker, KeyCode.A); CheckEqual(Snapshot(held: Keys(KeyCode.A), pressed: Keys(KeyCode.A)), keyboard.Physical); } public void LayoutKey_ReplacesThePhysicalOneInLayoutSpace() { KeyboardSnapshot keyboard = Down(new KeyTracker(), KeyCode.A, KeyCode.Q); CheckEqual(Snapshot(held: Keys(KeyCode.A), pressed: Keys(KeyCode.A)), keyboard.Physical); CheckEqual(Snapshot(held: Keys(KeyCode.Q), pressed: Keys(KeyCode.Q)), keyboard.Layout); } public void NoLayoutKey_CountsAsItselfInLayoutSpace() { KeyboardSnapshot keyboard = Down(new KeyTracker(), KeyCode.ArrowLeft); CheckEqual(keyboard.Physical, keyboard.Layout); } public void RepeatOfAHeldKey_KeepsTheLayoutKeyItWentDownWith() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A, KeyCode.Q); KeyboardSnapshot keyboard = Repeat(tracker, KeyCode.A, KeyCode.W); CheckEqual(Snapshot(held: Keys(KeyCode.Q), repeated: Keys(KeyCode.Q)), keyboard.Layout); } public void TwoKeysWithTheSameLayoutKey_PressItTwice() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A, KeyCode.Q); KeyboardSnapshot keyboard = Down(tracker, KeyCode.Q, KeyCode.Q); CheckEqual(Snapshot(held: Keys(KeyCode.Q), pressed: Keys(KeyCode.Q)), keyboard.Layout); } public void NoneKey_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); Down(new KeyTracker(), KeyCode.None); } public void KeyBeyondWhatKeyStateHolds_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); Down(new KeyTracker(), (KeyCode)KeyState.Capacity); } public void InvalidLayoutKey_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); Down(new KeyTracker(), KeyCode.A, (KeyCode)KeyState.Capacity); } public void InvalidLayoutKey_ChangesNothing() { var tracker = new KeyTracker(); try { Down(tracker, KeyCode.A, (KeyCode)KeyState.Capacity); } catch (ArgumentOutOfRangeException) { } CheckEqual(default(KeyState), tracker.Held); } } public class KeyUpTests : TestFixture { public void HeldKey_IsReleasedAndNoLongerHeld() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A); KeyboardSnapshot keyboard = tracker.KeyUp(KeyCode.A); CheckEqual(Snapshot(released: Keys(KeyCode.A)), keyboard.Physical); } public void OtherHeldKeys_StayHeld() { var tracker = new KeyTracker(); Down(tracker, KeyCode.ControlLeft); Down(tracker, KeyCode.Z); KeyboardSnapshot keyboard = tracker.KeyUp(KeyCode.Z); CheckEqual(Snapshot(held: Keys(KeyCode.ControlLeft), released: Keys(KeyCode.Z)), keyboard.Physical); } public void LayoutKey_IsReleasedInLayoutSpace() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A, KeyCode.Q); KeyboardSnapshot keyboard = tracker.KeyUp(KeyCode.A); CheckEqual(Snapshot(released: Keys(KeyCode.Q)), keyboard.Layout); } public void LayoutKeyFromKeyDown_IsTheOneReleased() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A, KeyCode.Q); Repeat(tracker, KeyCode.A, KeyCode.W); KeyboardSnapshot keyboard = tracker.KeyUp(KeyCode.A); CheckEqual(Snapshot(released: Keys(KeyCode.Q)), keyboard.Layout); } public void TwoKeysWithTheSameLayoutKey_KeepItHeldUntilBothAreUp() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A, KeyCode.Q); Down(tracker, KeyCode.Q, KeyCode.Q); KeyboardSnapshot first = tracker.KeyUp(KeyCode.A); KeyboardSnapshot second = tracker.KeyUp(KeyCode.Q); CheckEqual(Snapshot(held: Keys(KeyCode.Q)), first.Layout); CheckEqual(Snapshot(released: Keys(KeyCode.Q)), second.Layout); } public void KeyNotSeenGoingDown_ChangesNothing() { var tracker = new KeyTracker(); Down(tracker, KeyCode.B, KeyCode.W); KeyboardSnapshot keyboard = tracker.KeyUp(KeyCode.A); CheckEqual(Snapshot(held: Keys(KeyCode.B)), keyboard.Physical); CheckEqual(Snapshot(held: Keys(KeyCode.W)), keyboard.Layout); CheckEqual(default(KeyState), tracker.NextFrame().Physical.Released); } public void NoneKey_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new KeyTracker().KeyUp(KeyCode.None); } } public class ResetTests : TestFixture { public void ReleasesEveryHeldKeyInBothSpaces() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A, KeyCode.Q); Down(tracker, KeyCode.ArrowLeft); KeyboardSnapshot keyboard = tracker.Reset(); CheckEqual(Snapshot(released: Keys(KeyCode.A, KeyCode.ArrowLeft)), keyboard.Physical); CheckEqual(Snapshot(released: Keys(KeyCode.Q, KeyCode.ArrowLeft)), keyboard.Layout); CheckEqual(default(KeyState), tracker.Held); } public void NothingHeld_ReleasesNothing() { CheckEqual(new KeyboardSnapshot(Snapshot(), Snapshot()), new KeyTracker().Reset()); } public void ForgetsTheLayoutKeysOfTheKeysItReleased() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A, KeyCode.Q); tracker.Reset(); Down(tracker, KeyCode.A, KeyCode.W); KeyboardSnapshot keyboard = tracker.KeyUp(KeyCode.A); CheckEqual(Snapshot(released: Keys(KeyCode.W)), keyboard.Layout); } public void ShowsUpInTheNextFrameAsReleases() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A); tracker.NextFrame(); tracker.Reset(); CheckEqual(Snapshot(released: Keys(KeyCode.A)), tracker.NextFrame().Physical); } public void KeyPressedEarlierInTheFrame_IsPressedAndReleasedInIt() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A); tracker.Reset(); CheckEqual(Snapshot(pressed: Keys(KeyCode.A), released: Keys(KeyCode.A)), tracker.NextFrame().Physical); } public void DropsRepeatsFromTheFrame() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A); tracker.NextFrame(); Repeat(tracker, KeyCode.A); tracker.Reset(); CheckEqual(default(KeyState), tracker.NextFrame().Physical.Repeated); } } public class HeldTests : TestFixture { public void ListsThePhysicalKeysHeldNow() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A, KeyCode.Q); Down(tracker, KeyCode.B); tracker.KeyUp(KeyCode.A); CheckEqual(Keys(KeyCode.B), tracker.Held); } public void IsEmptyAtTheStart() { CheckEqual(default(KeyState), new KeyTracker().Held); } } public class NextFrameTests : TestFixture { public void NothingHappened_IsEmpty() { CheckEqual(new KeyboardSnapshot(Snapshot(), Snapshot()), new KeyTracker().NextFrame()); } public void CollectsEveryEdgeSinceTheLastFrame() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A); Down(tracker, KeyCode.B); KeyboardSnapshot frame = tracker.NextFrame(); CheckEqual(Snapshot(held: Keys(KeyCode.A, KeyCode.B), pressed: Keys(KeyCode.A, KeyCode.B)), frame.Physical); } public void EdgesLastOneFrame_ButHeldKeysCarryOver() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A); tracker.NextFrame(); CheckEqual(Snapshot(held: Keys(KeyCode.A)), tracker.NextFrame().Physical); } public void TapWithinAFrame_IsPressedAndReleasedButNotHeld() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A); tracker.KeyUp(KeyCode.A); CheckEqual(Snapshot(pressed: Keys(KeyCode.A), released: Keys(KeyCode.A)), tracker.NextFrame().Physical); } public void ReleaseThenPressAgain_IsHeldPressedAndReleased() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A); tracker.NextFrame(); tracker.KeyUp(KeyCode.A); Down(tracker, KeyCode.A); CheckEqual(Snapshot(held: Keys(KeyCode.A), pressed: Keys(KeyCode.A), released: Keys(KeyCode.A)), tracker.NextFrame().Physical); } public void SeveralRepeats_ShowUpAsOne() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A); tracker.NextFrame(); Repeat(tracker, KeyCode.A); Repeat(tracker, KeyCode.A); Repeat(tracker, KeyCode.A); CheckEqual(Snapshot(held: Keys(KeyCode.A), repeated: Keys(KeyCode.A)), tracker.NextFrame().Physical); } public void RepeatAfterThePressInTheSameFrame_IsNotRecorded() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A); Repeat(tracker, KeyCode.A); CheckEqual(Snapshot(held: Keys(KeyCode.A), pressed: Keys(KeyCode.A)), tracker.NextFrame().Physical); } public void RepeatAfterAReleaseInTheSameFrame_CountsAsAPress() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A); tracker.NextFrame(); tracker.KeyUp(KeyCode.A); Repeat(tracker, KeyCode.A); CheckEqual(Snapshot(held: Keys(KeyCode.A), pressed: Keys(KeyCode.A), released: Keys(KeyCode.A)), tracker.NextFrame().Physical); } public void ReleaseAfterARepeat_DropsTheRepeat() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A); tracker.NextFrame(); Repeat(tracker, KeyCode.A); tracker.KeyUp(KeyCode.A); CheckEqual(Snapshot(released: Keys(KeyCode.A)), tracker.NextFrame().Physical); } public void PressAfterARepeat_DropsTheRepeat() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A); tracker.NextFrame(); Repeat(tracker, KeyCode.A); Down(tracker, KeyCode.A); CheckEqual(Snapshot(held: Keys(KeyCode.A), pressed: Keys(KeyCode.A)), tracker.NextFrame().Physical); } public void LayoutSpace_CollectsTheSameWayWithLayoutKeys() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A, KeyCode.Q); Down(tracker, KeyCode.ArrowLeft); tracker.KeyUp(KeyCode.ArrowLeft); CheckEqual(Snapshot(held: Keys(KeyCode.Q), pressed: Keys(KeyCode.Q, KeyCode.ArrowLeft), released: Keys(KeyCode.ArrowLeft)), tracker.NextFrame().Layout); } public void EventsAfterwards_BelongToTheFollowingFrame() { var tracker = new KeyTracker(); Down(tracker, KeyCode.A); KeyboardSnapshot first = tracker.NextFrame(); Down(tracker, KeyCode.B); KeyboardSnapshot second = tracker.NextFrame(); CheckEqual(Snapshot(held: Keys(KeyCode.A), pressed: Keys(KeyCode.A)), first.Physical); CheckEqual(Snapshot(held: Keys(KeyCode.A, KeyCode.B), pressed: Keys(KeyCode.B)), second.Physical); } } public class WithChordsTests : TestFixture { public void ShortcutMatchesTheEventThatTriggersIt() { var tracker = new KeyTracker(); Down(tracker, KeyCode.ControlRight); Chord undo = Chord.PressOrRepeat(KeyCode.Z).WithAnyHeld(KeyState.Control); Check(undo.Matches(Down(tracker, KeyCode.Z).Physical)); Check(undo.Matches(Repeat(tracker, KeyCode.Z).Physical)); } public void ShortcutDoesNotMatchTheModifierGoingDown() { var tracker = new KeyTracker(); Down(tracker, KeyCode.Z); Chord undo = Chord.PressOrRepeat(KeyCode.Z).WithAnyHeld(KeyState.Control); CheckFalse(undo.Matches(Down(tracker, KeyCode.ControlLeft).Physical)); } public void LayoutChord_FollowsTheLayoutWhilePhysicalFollowsThePosition() { var tracker = new KeyTracker(); KeyboardSnapshot keyboard = Down(tracker, KeyCode.A, KeyCode.Q); Check(Chord.Press(KeyCode.Q).Matches(keyboard.Layout)); CheckFalse(Chord.Press(KeyCode.Q).Matches(keyboard.Physical)); Check(Chord.Press(KeyCode.A).Matches(keyboard.Physical)); } public void PollingChord_SeesATapThatHappenedBetweenFrames() { var tracker = new KeyTracker(); Down(tracker, KeyCode.Z); tracker.KeyUp(KeyCode.Z); Check(Chord.Press(KeyCode.Z).Matches(tracker.NextFrame().Physical)); } } } }