using AcDream.App.UI; using AcDream.App.UI.Layout; namespace AcDream.App.Tests.UI.Layout; public class ElementReaderTests { // ── ToAnchors ──────────────────────────────────────────────────────────── /// /// Edge value 4 = stretch (pinned to BOTH near AND far sides simultaneously). /// LeftEdge=4 → Left anchor; RightEdge=4 → Right anchor. /// TopEdge=1 → Top only (near-pin); BottomEdge=1 → near-pin (left/top axis), NOT Bottom. /// [Fact] public void EdgeFlagsToAnchors_LeftRight_Stretches() { // left=4 (stretch ⇒ Left), top=1 (near-pin ⇒ Top), right=4 (stretch ⇒ Right), bottom=1 (near-pin of bottom axis ⇒ not Bottom) var a = ElementReader.ToAnchors(left: 4, top: 1, right: 4, bottom: 1); Assert.True(a.HasFlag(AnchorEdges.Left)); Assert.True(a.HasFlag(AnchorEdges.Right)); Assert.False(a.HasFlag(AnchorEdges.Bottom)); } /// /// Edge value 1 = pinned to the NEAR edge of that axis. /// For LeftEdge: near = Left. For TopEdge: near = Top. /// For RightEdge: value 1 means near-pin of the right axis → does NOT map to Right anchor. /// For BottomEdge: value 1 means near-pin of the bottom axis → does NOT map to Bottom anchor. /// [Fact] public void EdgeFlagsToAnchors_AllOnes_PinsTopLeftOnly() { // 1 everywhere: only Left and Top anchors set (near-pins). Right/Bottom are far edges and value 1 is near-pin. var a = ElementReader.ToAnchors(left: 1, top: 1, right: 1, bottom: 1); Assert.True(a.HasFlag(AnchorEdges.Left)); Assert.True(a.HasFlag(AnchorEdges.Top)); Assert.False(a.HasFlag(AnchorEdges.Right)); Assert.False(a.HasFlag(AnchorEdges.Bottom)); } /// /// Edge value 2 = pinned to the FAR edge of that axis. /// For RightEdge: far = Right anchor. For BottomEdge: far = Bottom anchor. /// For LeftEdge: value 2 means far-pin of the left axis → does NOT map to Left anchor. /// For TopEdge: value 2 means far-pin of the top axis → does NOT map to Top anchor. /// [Fact] public void EdgeFlagsToAnchors_AllTwos_PinsRightBottomOnly() { // 2 everywhere: only Right and Bottom anchors set (far-pins). var a = ElementReader.ToAnchors(left: 2, top: 2, right: 2, bottom: 2); Assert.False(a.HasFlag(AnchorEdges.Left)); Assert.False(a.HasFlag(AnchorEdges.Top)); Assert.True(a.HasFlag(AnchorEdges.Right)); Assert.True(a.HasFlag(AnchorEdges.Bottom)); } /// /// All-zero edge flags (prototype-only elements) fall back to Left|Top default. /// [Fact] public void EdgeFlagsToAnchors_AllZero_DefaultsToTopLeft() { var a = ElementReader.ToAnchors(left: 0, top: 0, right: 0, bottom: 0); Assert.Equal(AnchorEdges.Left | AnchorEdges.Top, a); } /// /// Value 3 = floating/centered between both far edges on that axis. /// Both LeftEdge=3 and RightEdge=3 → neither Left nor Right are set by the /// near/stretch rules. The result is only Right+Bottom (the "far" semantics). /// Specifically: left=3 → not Left (3 is not 1 or 4); right=3 → Right (3 is not 2 or 4, skip). /// Wait — value 3 means "pinned to BOTH far edges" per format doc §4. Re-check the /// mapping rule: Right anchor fires on right==2 || right==4, NOT on right==3. /// So value 3 on LeftEdge, TopEdge, RightEdge, BottomEdge → no flags set → default Left|Top. /// This test covers that corner case (element 0x100004A9 — expand-detail overlay). /// [Fact] public void EdgeFlagsToAnchors_ValueThree_FallsBackToTopLeft() { // value 3 doesn't match any anchor rule; falls back to Left|Top default. var a = ElementReader.ToAnchors(left: 3, top: 3, right: 3, bottom: 3); Assert.Equal(AnchorEdges.Left | AnchorEdges.Top, a); } // ── Merge ──────────────────────────────────────────────────────────────── [Fact] public void Merge_BaseThenOverride_DerivedWins() { var base_ = new ElementInfo { Type = 0, FontDid = 0x40000000, Width = 150, Height = 16 }; var derived = new ElementInfo { Type = 0, Width = 200 }; // overrides width, inherits font + height var merged = ElementReader.Merge(base_, derived); Assert.Equal(200, merged.Width); // override Assert.Equal(16, merged.Height); // inherited Assert.Equal(0x40000000u, merged.FontDid);// inherited } [Fact] public void Merge_DerivedHasFontDid_OverridesBase() { var base_ = new ElementInfo { FontDid = 0x40000000, Width = 100, Height = 10 }; var derived = new ElementInfo { FontDid = 0x40000001, Width = 100 }; var merged = ElementReader.Merge(base_, derived); Assert.Equal(0x40000001u, merged.FontDid); } [Fact] public void Merge_DerivedStateMediaOverridesBase() { var base_ = new ElementInfo(); base_.StateMedia[""] = (0x06001000u, 1); base_.StateMedia["HideDetail"] = (0x06001001u, 1); var derived = new ElementInfo(); derived.StateMedia[""] = (0x06002000u, 3); // overrides base default state var merged = ElementReader.Merge(base_, derived); // derived's "" overrides base's "" Assert.Equal((0x06002000u, 3), merged.StateMedia[""]); // base's "HideDetail" is kept (derived didn't provide it) Assert.Equal((0x06001001u, 1), merged.StateMedia["HideDetail"]); } [Fact] public void Merge_ChildrenComeFromDerived() { var base_ = new ElementInfo(); base_.Children.Add(new ElementInfo { Id = 0x1u }); var derived = new ElementInfo(); derived.Children.Add(new ElementInfo { Id = 0x2u }); var merged = ElementReader.Merge(base_, derived); // children must come from derived only Assert.Single(merged.Children); Assert.Equal(0x2u, merged.Children[0].Id); } }