using System.Xml.Linq; using XFEEDToolBox.Core.Models; using XFEEDToolBox.Core.Navigation; using XFEEDToolBox.Core.Notifications; using XFEExtension.NetCore.XUnit.Attributes; namespace XFEEDToolBox.Test; [TestFixture] public class NavigationArrivalTests { private static readonly DateTimeOffset At=DateTimeOffset.Parse("2026-10-23T04:00:00Z"); private static PlayerState Player()=>new(){InGame=true,CommanderId="commander",LoadGameAt=At,SystemName="Sol",Navigation=new(){CurrentSystemAddress=42}}; private static NavigationProgress Progress(NavigationRunState state,string destination="Sol")=>new(state,destination,"已抵达",""); [Test] public void OnlyConfirmedCompletionNotifiesOnceAndAnotherRunCanNotifyAgain() { var observer=new NavigationArrivalObserver();var player=Player(); Check.That(observer.Observe(Progress(NavigationRunState.Completed),player,At) is null); Check.That(observer.Observe(Progress(NavigationRunState.Running),player,At) is null); var arrival=observer.Observe(Progress(NavigationRunState.Completed),player,At)!; Check.Equal(42L,arrival.SystemAddress);Check.Equal("Sol",arrival.Destination); Check.That(observer.Observe(Progress(NavigationRunState.Completed),player,At) is null); observer.Observe(Progress(NavigationRunState.Running),player,At); Check.That(observer.Observe(Progress(NavigationRunState.Completed),player,At) is not null); } [Test] public void StopsErrorsWrongSystemsAndUnconfirmedJumpsDoNotNotify() { foreach(var state in new[]{NavigationRunState.Stopped,NavigationRunState.Failed,NavigationRunState.Idle}) { var observer=new NavigationArrivalObserver();observer.Observe(Progress(NavigationRunState.Running),Player(),At); Check.That(observer.Observe(Progress(state),Player(),At) is null); } foreach(var player in new[]{Player() with{InGame=false},Player() with{SystemName="Achenar"},Player() with{Navigation=new(){CurrentSystemAddress=42,JumpInProgress=true}}}) { var observer=new NavigationArrivalObserver();observer.Observe(Progress(NavigationRunState.Running),Player(),At); Check.That(observer.Observe(Progress(NavigationRunState.Completed),player,At) is null); } } [Test] public void StationArrivalRequiresTheConfirmedRequestedDockingPort() { var target=new SystemNavigationTarget(42,99,"Galileo",SystemTargetKind.Station,500); foreach(var player in new[]{Player(),Player() with{Docked=true,MarketId=600}}) { var observer=new NavigationArrivalObserver();observer.Observe(new SystemNavigationProgress(NavigationRunState.Running,target,SystemNavigationPhase.NormalApproach,""),player,At); Check.That(observer.Observe(new SystemNavigationProgress(NavigationRunState.Completed,target,SystemNavigationPhase.Completed,""),player,At) is null); } var docked=Player() with{Docked=true,MarketId=500};var success=new NavigationArrivalObserver(); success.Observe(new SystemNavigationProgress(NavigationRunState.Running,target,SystemNavigationPhase.NativeLanding,""),docked,At); var arrival=success.Observe(new SystemNavigationProgress(NavigationRunState.Completed,target,SystemNavigationPhase.Completed,""),docked,At)!; Check.Equal("Galileo",arrival.Destination);Check.Equal("current-system",arrival.Page);Check.That(arrival.Docked); } [Test] public void ScreenshotSceneIsBoundToTheCommanderSessionAndSystem() { var player=Player();var observer=new NavigationArrivalObserver();observer.Observe(Progress(NavigationRunState.Running),player,At); var arrival=observer.Observe(Progress(NavigationRunState.Completed),player,At)!; Check.That(arrival.SameScene(player));Check.That(!arrival.SameScene(player with{CommanderId="other"})); Check.That(!arrival.SameScene(player with{LoadGameAt=At.AddHours(1)})); Check.That(!arrival.SameScene(player with{Navigation=new(){CurrentSystemAddress=99}})); } [Test] public void ACompletionFromAnOlderCommanderOrSessionCannotNotifyTheNewSession() { foreach(var changed in new[]{Player() with{CommanderId="other"},Player() with{LoadGameAt=At.AddHours(1)}}) { var observer=new NavigationArrivalObserver();observer.Observe(Progress(NavigationRunState.Running),Player(),At); Check.That(observer.Observe(Progress(NavigationRunState.Completed),changed,At) is null); } } [Test] public void NativeToastEscapesTextAndUsesTheLocalFullWidthHeroAndClickArguments() { var arrival=new NavigationArrival("id",42,"A & B","",false,false,"",At,At); var image=Path.GetFullPath("arrival with space & name.png");var xml=ArrivalToastContent.Build(arrival,image);var toast=XElement.Parse(xml); Check.Equal("已抵达 ",toast.Descendants("text").First().Value); Check.Equal("hero",toast.Descendants("image").Single().Attribute("placement")!.Value); Check.Equal(new Uri(image).AbsoluteUri,toast.Descendants("image").Single().Attribute("src")!.Value); Check.Equal("action=arrival&page=navigation&system=42",toast.Attribute("launch")!.Value); Check.That(!XElement.Parse(ArrivalToastContent.Build(arrival)).Descendants("image").Any()); } [Test] public void BrightWarmAndBlueStellarDiscsAreRecognized() { foreach(var colour in new[]{(R:(byte)255,G:(byte)130,B:(byte)30),(R:(byte)150,G:(byte)185,B:(byte)255)}) { var pixels=Frame((x,y)=>(x-160)*(x-160)+(y-55)*(y-55)<18*18?colour:((byte)2,(byte)3,(byte)8)); Check.That(StarFrameCandidate.HasStar(320,180,pixels)); } } [Test] public void BlankTransitionAndBottomHudFramesCannotBeUsedAsStarPictures() { foreach(var pixels in new[]{Frame((_,_)=>(0,0,0)),Frame((_,_)=>(255,255,255)),Frame((x,y)=>y>160&&x is >40 and <280?((byte)255,(byte)180,(byte)20):((byte)2,(byte)3,(byte)8))}) Check.That(!StarFrameCandidate.HasStar(320,180,pixels)); } [Test] public void InvalidScreenshotDimensionsAreRejected() { Check.That(!StarFrameCandidate.HasStar(1,1,new byte[4]));Check.That(!StarFrameCandidate.HasStar(320,180,new byte[8])); } private static byte[] Frame(Func pixel) { var pixels=new byte[320*180*4]; for(var y=0;y<180;y++)for(var x=0;x<320;x++){var (r,g,b)=pixel(x,y);var p=(y*320+x)*4;pixels[p]=b;pixels[p+1]=g;pixels[p+2]=r;pixels[p+3]=255;} return pixels; } }