using AcDream.App.UI; using AcDream.App.UI.Layout; using AcDream.App.Tests.UI.Layout; using AcDream.Core.Net.Messages; namespace AcDream.App.Tests.UI; public sealed class GameplayConfirmationControllerTests { [Fact] public void SkillRequestAppendsContinueAndCloseNoticeSendsServerTuple() { var root = new UiRoot { Width = 800f, Height = 600f }; ImportedLayout? shown = null; var factory = new RetailDialogFactory(root, _ => shown = FixtureLoader.LoadConfirmationDialog()); var responses = new List<(uint Type, uint Context, bool Accepted)>(); using var controller = new GameplayConfirmationController( factory, (type, context, accepted) => responses.Add((type, context, accepted))); Assert.True(controller.HandleRequest( new GameEvents.CharacterConfirmationRequest(2u, 42u, "Raise this skill?"))); Assert.Equal( "Raise this skill? Continue?", string.Join(" ", Assert.IsType(shown!.FindElement( RetailConfirmationDialogView.MessageElementId)).LinesProvider().Select(static line => line.Text))); Assert.IsType(shown.FindElement( RetailConfirmationDialogView.AcceptButtonId)).OnClick!(); Assert.Equal([(2u, 42u, true)], responses); Assert.Equal(0u, controller.ActiveDialogContext); } /// /// MUST-FIX 2 (FA4 fix round, 2026-08-12): a fellowship invite /// (ConfirmationType.Fellowship, type 4) reaches this generic /// controller and opens a dialog exactly like any other type — no /// client-side interceptor exists anymore (D6's correction: retail's /// client reads neither option bit on the invite path; /// RetailUiRuntime.HandleConfirmationRequest now routes every /// type, including 4, straight here). Type 4 is NOT in the /// 2/3/5/6 " Continue?"-suffix set, so the message renders verbatim — /// matching Handle_Character__ConfirmationRequest @0x005640A0's /// case-4 arm, which is a single call with no text transformation. /// [Fact] public void FellowshipInviteRequest_Type4_OpensDialog_MessageVerbatim_AndSendsAcceptOnClose() { var root = new UiRoot { Width = 800f, Height = 600f }; ImportedLayout? shown = null; var factory = new RetailDialogFactory(root, _ => shown = FixtureLoader.LoadConfirmationDialog()); var responses = new List<(uint Type, uint Context, bool Accepted)>(); using var controller = new GameplayConfirmationController( factory, (type, context, accepted) => responses.Add((type, context, accepted))); Assert.True(controller.HandleRequest( new GameEvents.CharacterConfirmationRequest(4u, 7u, "Alice invites you to join their fellowship."))); Assert.Equal( "Alice invites you to join their fellowship.", string.Join(" ", Assert.IsType(shown!.FindElement( RetailConfirmationDialogView.MessageElementId)).LinesProvider().Select(static line => line.Text))); Assert.IsType(shown.FindElement( RetailConfirmationDialogView.AcceptButtonId)).OnClick!(); Assert.Equal([(4u, 7u, true)], responses); Assert.Equal(0u, controller.ActiveDialogContext); } /// /// Campaign FA slice FA5, item 3: verifies the allegiance-swear /// confirmation (ConfirmationType.AllegianceSwear, type 1 — /// RecvNotice_SwearAllegianceRequest -> /// Handle_Character__ConfirmationRequest @0x005640A0's case 1) /// reaches this SAME generic controller, mirroring the type-4 test /// above exactly — no allegiance-specific intercept exists (there /// never was one to remove; D6's fellowship correction did not touch /// type 1 at all, but FA5's own contract calls for this explicit check /// since SocialAllegiancePageController is the new panel that /// makes this path reachable). Type 1 is NOT in the 2/3/5/6 " Continue?" /// suffix set, so the message renders verbatim — ACE's own type-1 /// message is the target's BARE name (lane C §6.4: /// Player_Allegiance.cs:91/ConfirmationManager.cs:38), not /// a full sentence, which this test's message deliberately mirrors /// rather than inventing retail's unported StringInfo-wrapped /// sentence (AD-85). /// [Fact] public void AllegianceSwearRequest_Type1_OpensDialog_MessageVerbatim_AndSendsAcceptOnClose() { var root = new UiRoot { Width = 800f, Height = 600f }; ImportedLayout? shown = null; var factory = new RetailDialogFactory(root, _ => shown = FixtureLoader.LoadConfirmationDialog()); var responses = new List<(uint Type, uint Context, bool Accepted)>(); using var controller = new GameplayConfirmationController( factory, (type, context, accepted) => responses.Add((type, context, accepted))); Assert.True(controller.HandleRequest( new GameEvents.CharacterConfirmationRequest(1u, 13u, "Bob"))); Assert.Equal( "Bob", string.Join(" ", Assert.IsType(shown!.FindElement( RetailConfirmationDialogView.MessageElementId)).LinesProvider().Select(static line => line.Text))); Assert.IsType(shown.FindElement( RetailConfirmationDialogView.AcceptButtonId)).OnClick!(); Assert.Equal([(1u, 13u, true)], responses); Assert.Equal(0u, controller.ActiveDialogContext); } [Fact] public void MatchingConfirmationDoneClosesDialogAndUnmatchedTupleDoesNothing() { var root = new UiRoot { Width = 800f, Height = 600f }; var factory = new RetailDialogFactory(root, _ => FixtureLoader.LoadConfirmationDialog()); var responses = new List<(uint Type, uint Context, bool Accepted)>(); using var controller = new GameplayConfirmationController( factory, (type, context, accepted) => responses.Add((type, context, accepted))); controller.HandleRequest(new GameEvents.CharacterConfirmationRequest(7u, 99u, "Proceed?")); Assert.False(controller.HandleDone( new GameEvents.CharacterConfirmationDone(7u, 100u))); Assert.True(factory.IsOpen); Assert.True(controller.HandleDone( new GameEvents.CharacterConfirmationDone(7u, 99u))); Assert.False(factory.IsOpen); Assert.Equal([(7u, 99u, false)], responses); } [Fact] public void FactoryReset_CompletesResponseBeforeSessionTupleIsForgotten() { var root = new UiRoot { Width = 800f, Height = 600f }; var factory = new RetailDialogFactory(root, _ => FixtureLoader.LoadConfirmationDialog()); var responses = new List<(uint Type, uint Context, bool Accepted)>(); using var controller = new GameplayConfirmationController( factory, (type, context, accepted) => responses.Add((type, context, accepted))); controller.HandleRequest( new GameEvents.CharacterConfirmationRequest(7u, 99u, "Proceed?")); factory.Reset(); controller.ResetSession(); Assert.Equal(0u, controller.ActiveDialogContext); Assert.Equal([(7u, 99u, false)], responses); Assert.False(factory.IsOpen); } }