using Annulus; using Azimuth; using System; using System.Collections.Generic; using UnitTestSharp; namespace Glass.UnitTests { public class StatefulMaterialTests : TestFixture { public class CounterState { public int Count; } public class SubCounterState : CounterState { } public class OtherState { } public class StatefulPanel : Panel { public void Set(StatefulMaterial material, StateType state) where StateType : class { SetMaterial(material, state); } public void SetDynamic(Material material, object state) { SetMaterialDynamic(material, state); } } public record CounterMaterial : StatefulMaterial { } public record OtherMaterial : StatefulMaterial { } public record ParentMaterial : Material { public ParentMaterial(params Material[] children) { ChildMaterials = children; } } public record LoopingMaterial : Material { public override IReadOnlyList ChildMaterials => new Material[] { this }; } public static readonly Rect Area = Rect.FromPositionAndSize(Vector.Zero, new Vector(10, 10)); public class RequiredStateTypeTests : TestFixture { public void Plain_IsNone() { CheckNull(new Material().RequiredStateType); } public void Stateful_IsItsStateType() { CheckEqual(typeof(CounterState), new CounterMaterial().RequiredStateType); } } public class RequiredByTests : TestFixture { public void NullRoot_IsNone() { CheckNull(MaterialStates.RequiredBy(null, out Material needy)); CheckNull(needy); } public void PlainTree_IsNone() { CheckNull(MaterialStates.RequiredBy(new ParentMaterial(new Material()), out Material needy)); CheckNull(needy); } public void StatefulRoot_IsItsOwnState() { var root = new CounterMaterial(); CheckEqual(typeof(CounterState), MaterialStates.RequiredBy(root, out Material needy)); CheckTrue(ReferenceEquals(root, needy)); } public void StatefulChild_IsFoundBelowAPlainParent() { var child = new CounterMaterial(); var root = new ParentMaterial(new Material(), new ParentMaterial(child)); CheckEqual(typeof(CounterState), MaterialStates.RequiredBy(root, out Material needy)); CheckTrue(ReferenceEquals(child, needy)); } public void NullChildren_AreSkipped() { var root = new ParentMaterial(null, new CounterMaterial()); CheckEqual(typeof(CounterState), MaterialStates.RequiredBy(root, out Material needy)); } public void SameStateTwice_IsFine() { var root = new ParentMaterial(new CounterMaterial(), new CounterMaterial()); CheckEqual(typeof(CounterState), MaterialStates.RequiredBy(root, out Material needy)); } public void DifferentStates_Throw() { var root = new ParentMaterial(new CounterMaterial(), new OtherMaterial()); CheckThrow(typeof(InvalidOperationException)); MaterialStates.RequiredBy(root, out Material needy); } public void ParentAndChildNeedingDifferentStates_Throw() { var root = new CounterMaterial() with { ChildMaterials = new Material[] { new OtherMaterial() } }; CheckThrow(typeof(InvalidOperationException)); MaterialStates.RequiredBy(root, out Material needy); } public void ATreeThatLoops_Throws() { CheckThrow(typeof(InvalidOperationException)); MaterialStates.RequiredBy(new LoopingMaterial(), out Material needy); } } public class CheckTests : TestFixture { public void PlainMaterialWithNoState_Passes() { MaterialStates.Check(new Material(), null); } public void NullMaterialWithNoState_Passes() { MaterialStates.Check(null, null); } public void PlainMaterialWithState_Throws() { CheckThrow(typeof(InvalidOperationException)); MaterialStates.Check(new Material(), new CounterState()); } public void NullMaterialWithState_Throws() { CheckThrow(typeof(InvalidOperationException)); MaterialStates.Check(null, new CounterState()); } public void StatefulMaterialWithNoState_Throws() { CheckThrow(typeof(InvalidOperationException)); MaterialStates.Check(new CounterMaterial(), null); } public void StatefulMaterialWithWrongState_Throws() { CheckThrow(typeof(InvalidOperationException)); MaterialStates.Check(new CounterMaterial(), new OtherState()); } public void StatefulMaterialWithItsState_Passes() { MaterialStates.Check(new CounterMaterial(), new CounterState()); } public void StatefulMaterialWithADerivedState_Passes() { MaterialStates.Check(new CounterMaterial(), new SubCounterState()); } public void BuriedStatefulMaterialWithNoState_Throws() { CheckThrow(typeof(InvalidOperationException)); MaterialStates.Check(new ParentMaterial(new CounterMaterial()), null); } public void BuriedStatefulMaterialWithItsState_Passes() { MaterialStates.Check(new ParentMaterial(new CounterMaterial()), new CounterState()); } public void TheMessageNamesTheMaterialAndTheStates() { try { MaterialStates.Check(new CounterMaterial(), new OtherState()); } catch (InvalidOperationException exception) { CheckTrue(exception.Message.Contains(nameof(CounterMaterial))); CheckTrue(exception.Message.Contains(nameof(CounterState))); CheckTrue(exception.Message.Contains(nameof(OtherState))); return; } CheckTrue(false); } } public class SetMaterialTests : TestFixture { public void Generic_SetsTheMaterialAndItsState() { var panel = new StatefulPanel(); var material = new CounterMaterial(); var state = new CounterState(); panel.Set(material, state); CheckTrue(ReferenceEquals(material, panel.Material)); CheckTrue(ReferenceEquals(state, panel.MaterialState)); } public void PlainSet_OfAStatefulMaterial_Throws() { var panel = new StatefulPanel(); CheckThrow(typeof(InvalidOperationException)); panel.Material = new CounterMaterial(); } public void FailedPlainSet_KeepsWhatWasThere() { var panel = new StatefulPanel(); var material = new CounterMaterial(); var state = new CounterState(); panel.Set(material, state); try { panel.Material = new OtherMaterial(); } catch (InvalidOperationException) { } CheckTrue(ReferenceEquals(material, panel.Material)); CheckTrue(ReferenceEquals(state, panel.MaterialState)); } public void PlainSet_OfAPlainMaterial_ClearsTheState() { var panel = new StatefulPanel(); panel.Set(new CounterMaterial(), new CounterState()); panel.Material = new Material(); CheckNull(panel.MaterialState); } public void Dynamic_WithTheRightState_Sets() { var panel = new StatefulPanel(); var state = new CounterState(); panel.SetDynamic(new CounterMaterial(), state); CheckTrue(ReferenceEquals(state, panel.MaterialState)); } public void Dynamic_WithTheWrongState_Throws() { var panel = new StatefulPanel(); CheckThrow(typeof(InvalidOperationException)); panel.SetDynamic(new CounterMaterial(), new OtherState()); } public void Dynamic_WithStateForAPlainMaterial_Throws() { var panel = new StatefulPanel(); CheckThrow(typeof(InvalidOperationException)); panel.SetDynamic(new Material(), new CounterState()); } public void Dynamic_WithNoStateForAStatefulMaterial_Throws() { var panel = new StatefulPanel(); CheckThrow(typeof(InvalidOperationException)); panel.SetDynamic(new CounterMaterial(), null); } public void AFailedSet_DoesNotLeakItsStateIntoThePlainSetAfterIt() { var panel = new StatefulPanel(); try { panel.SetDynamic(new CounterMaterial(), new OtherState()); } catch (InvalidOperationException) { } panel.Material = new Material(); CheckNull(panel.MaterialState); } public void NullMaterial_WithNoState_Sets() { var panel = new StatefulPanel(); panel.Set(new CounterMaterial(), new CounterState()); panel.SetDynamic(null, null); CheckNull(panel.Material); CheckNull(panel.MaterialState); } public void Stateful_IsFoundBelowAPlainMaterial() { var panel = new StatefulPanel(); CheckThrow(typeof(InvalidOperationException)); panel.Material = new ParentMaterial(new CounterMaterial()); } } public class DrawStateTests : TestFixture { private static RendererTests.TestRenderer RendererFor(List seen) { var renderer = new RendererTests.TestRenderer(); renderer.RegisterRenderCallback( (Renderer drawer, int context, CounterMaterial material, CounterState state, Rect rect, in DrawState drawState) => seen.Add(state)); return renderer; } private static ControlContainer Containing(params Control[] controls) { var returnMe = new ControlContainer(); foreach (Control control in controls) { control.Bounds = Area; returnMe.Add(control); } return returnMe; } public void TheCallback_ReceivesTheControlsStateTyped() { var seen = new List(); var state = new CounterState { Count = 5 }; var panel = new StatefulPanel(); panel.Set(new CounterMaterial(), state); RendererFor(seen).Render(7, Containing(panel), new DrawState()); CheckEqual(1, seen.Count); CheckTrue(ReferenceEquals(state, seen[0])); } public void TwoControlsSharingAMaterial_EachGetTheirOwnState() { var seen = new List(); var material = new CounterMaterial(); var first = new StatefulPanel(); var second = new StatefulPanel(); var firstState = new CounterState(); var secondState = new CounterState(); first.Set(material, firstState); second.Set(material, secondState); RendererFor(seen).Render(7, Containing(first, second), new DrawState()); CheckEqual(2, seen.Count); CheckTrue(ReferenceEquals(firstState, seen[0])); CheckTrue(ReferenceEquals(secondState, seen[1])); } public void AChildDoesNotInheritItsParentsState() { var renderer = new RendererTests.TestRenderer(); var parent = new StatefulPanel(); var child = new StatefulPanel { Bounds = Area, Material = new SolidColorMaterial() }; parent.CanvasChildren.Add(child); parent.Set(new CounterMaterial(), new CounterState()); object seenByChild = new object(); renderer.RegisterRenderCallback( (Renderer drawer, int context, SolidColorMaterial material, Rect rect, in DrawState drawState) => seenByChild = drawState.MaterialState); renderer.RegisterRenderCallback( (Renderer drawer, int context, CounterMaterial material, CounterState state, Rect rect, in DrawState drawState) => { }); renderer.Render(7, Containing(parent), new DrawState()); CheckNull(seenByChild); } public void DrawnOutsideAControl_WithNoState_Throws() { RendererTests.TestRenderer renderer = RendererFor(new List()); CheckThrow(typeof(InvalidOperationException)); renderer.Render(7, new CounterMaterial(), Area, new DrawState()); } public void DrawnOutsideAControl_WithTheWrongState_Throws() { RendererTests.TestRenderer renderer = RendererFor(new List()); CheckThrow(typeof(InvalidOperationException)); renderer.Render(7, new CounterMaterial(), Area, new DrawState { MaterialState = new OtherState() }); } public void DrawnOutsideAControl_WithTheState_ReachesTheCallback() { var seen = new List(); var state = new CounterState(); RendererFor(seen).Render(7, new CounterMaterial(), Area, new DrawState { MaterialState = state }); CheckTrue(ReferenceEquals(state, seen[0])); } } } }