using System.Collections.Generic; using AcDream.App.UI.Layout; namespace AcDream.App.Tests.UI.Layout; /// /// Retail's state media is a small program, and this plays it. /// public sealed class UiMediaSequenceTests { private static UiMediaStep Image(uint file) => new(UiMediaStepKind.Image, file, 1, 0f, 0f, 0u, 0f); private static UiMediaStep Pause(float seconds) => new(UiMediaStepKind.Pause, 0u, 0, seconds, seconds, 0u, 0f); private static UiMediaStep State(uint state, float probability = 1f) => new(UiMediaStepKind.State, 0u, 0, 0f, 0f, state, probability); private static UiMediaStep Jump(uint index, float probability = 1f) => new(UiMediaStepKind.Jump, 0u, 0, 0f, 0f, index, probability); /// /// The chat unseen-text indicator's authored sequence, verbatim from the /// installed dats (0x2100006F / 0x1000048C, state 1): two frames /// alternating every half second, three times, then hand off to Ghosted. /// private static IReadOnlyList BlinkSequence() => [ Image(0x06005F0Eu), Pause(0.5f), Image(0x06005F0Fu), Pause(0.5f), Image(0x06005F0Eu), Pause(0.5f), Image(0x06005F0Fu), Pause(0.5f), Image(0x06005F0Eu), Pause(0.5f), Image(0x06005F0Fu), Pause(0.5f), State(13u), ]; [Theory] [InlineData(0.0f, 0x06005F0Eu)] [InlineData(0.4f, 0x06005F0Eu)] [InlineData(0.5f, 0x06005F0Fu)] // first flip, exactly on the boundary [InlineData(0.9f, 0x06005F0Fu)] [InlineData(1.0f, 0x06005F0Eu)] [InlineData(2.75f, 0x06005F0Fu)] public void TheFrameAlternatesEveryHalfSecond(float elapsed, uint expected) { (uint file, uint? transition) = UiMediaSequence.Sample(BlinkSequence(), elapsed); Assert.Equal(expected, file); Assert.Null(transition); } [Fact] public void AfterThreeSecondsItHandsOffToGhosted() { // This is the behaviour nobody would guess from the code: retail's // indicator is a transient attention-flash, not a badge that stays // lit. Three seconds of blinking, then it hides itself. (uint _, uint? transition) = UiMediaSequence.Sample(BlinkSequence(), 3.0f); Assert.Equal(13u, transition); } [Fact] public void TheHandOffDoesNotFireEarly() { Assert.Null(UiMediaSequence.Sample(BlinkSequence(), 2.99f).TransitionState); } [Fact] public void ASingleImageIsNotAnAnimation() { // A still frame must keep the ordinary draw path rather than being // routed through a player that would only ever return the same file. Assert.False(UiMediaSequence.IsAnimated([Image(1u)])); Assert.False(UiMediaSequence.IsAnimated([])); Assert.False(UiMediaSequence.IsAnimated(null)); Assert.True(UiMediaSequence.IsAnimated([Image(1u), Pause(1f)])); Assert.True(UiMediaSequence.IsAnimated([Image(1u), Image(2u)])); } [Fact] public void AJumpLoopsAndKeepsRunningForever() { IReadOnlyList looping = [ Image(0xAAu), Pause(1f), Image(0xBBu), Pause(1f), Jump(0u), ]; Assert.Equal(0xAAu, UiMediaSequence.Sample(looping, 0.5f).File); Assert.Equal(0xBBu, UiMediaSequence.Sample(looping, 1.5f).File); Assert.Equal(0xAAu, UiMediaSequence.Sample(looping, 2.5f).File); // looped Assert.Equal(0xBBu, UiMediaSequence.Sample(looping, 101.5f).File); // still going } [Fact] public void AZeroTimeJumpCycleTerminatesInsteadOfHanging() { // A malformed sequence — a jump cycle with no pause in it — would spin // forever inside one frame. It must return, not hang the client. IReadOnlyList pathological = [Image(0xAAu), Jump(0u)]; Assert.Equal(0xAAu, UiMediaSequence.Sample(pathological, 1f).File); } [Fact] public void AnUncertainBranchFallsThroughRatherThanLooping() { // Falling through ends the sequence; treating it as "always" would // animate forever. The conservative direction is to stop. IReadOnlyList maybe = [ Image(0xAAu), Pause(1f), Jump(0u, probability: 0.5f), Image(0xBBu), ]; Assert.Equal(0xBBu, UiMediaSequence.Sample(maybe, 1.5f).File); } [Fact] public void UnknownStepsAreSteppedOverWithoutBreakingJumpIndices() { // Sound, movie and message entries are kept as Other precisely so a // jump's index still lands on the authored entry. IReadOnlyList withOther = [ new(UiMediaStepKind.Other, 0u, 0, 0f, 0f, 0u, 0f), Image(0xCCu), Pause(1f), Jump(1u), ]; Assert.Equal(0xCCu, UiMediaSequence.Sample(withOther, 0.5f).File); Assert.Equal(0xCCu, UiMediaSequence.Sample(withOther, 5f).File); } }