using AcDream.Launcher.Core.Launching;
using AcDream.Launcher.Core.Profiles;
using AcDream.Launcher.Core.Orchestration;
using AcDream.Launcher.ViewModels;
namespace AcDream.Launcher.Tests;
///
/// LU6. A sessions row answers "who is playing, and are they in yet?" — not
/// "which launch mode started this process".
///
public sealed class LauncherSessionRowViewModelTests
{
[Theory]
[InlineData(LauncherActivityState.Starting, "Starting")]
[InlineData(LauncherActivityState.Running, "Starting")]
[InlineData(LauncherActivityState.Connected, "Character select")]
[InlineData(LauncherActivityState.InWorld, "In game")]
[InlineData(LauncherActivityState.Disconnected, "Stopping")]
[InlineData(LauncherActivityState.Stopping, "Stopping")]
[InlineData(LauncherActivityState.Exited, "Stopped")]
[InlineData(LauncherActivityState.Cancelled, "Stopped")]
[InlineData(LauncherActivityState.Failed, "Failed")]
public void StateReadsAsPlainEnglish(LauncherActivityState state, string expected)
{
var row = new LauncherSessionRowViewModel(
Snapshot(state: state),
_ => Task.CompletedTask);
Assert.Equal(expected, row.State);
}
///
/// The same wording regardless of how the session was started: a Play
/// launch and a character-select launch both read "In game" once the
/// player is actually in the world, because the state comes from the
/// host's own status stream rather than from the launch mode.
///
[Theory]
[InlineData(LaunchMode.Gui)]
[InlineData(LaunchMode.GuiSelect)]
[InlineData(LaunchMode.Headless)]
public void InGameDoesNotDependOnHowTheSessionWasLaunched(LaunchMode mode)
{
var row = new LauncherSessionRowViewModel(
Snapshot(state: LauncherActivityState.InWorld, mode: mode),
_ => Task.CompletedTask);
Assert.Equal("In game", row.State);
}
[Fact]
public void ACharacterSelectLaunchSaysSoUntilACharacterIsKnown()
{
var row = new LauncherSessionRowViewModel(
Snapshot(character: null, mode: LaunchMode.GuiSelect),
_ => Task.CompletedTask);
Assert.Equal("Character select", row.Character);
Assert.Equal("testaccount", row.Account);
}
///
/// Once the host reports who entered the world, the orchestrator fills the
/// name in — so a character-select session stops being anonymous.
///
[Fact]
public void AnEnteredCharacterReplacesTheCharacterSelectPlaceholder()
{
var row = new LauncherSessionRowViewModel(
Snapshot(
character: "+alex",
mode: LaunchMode.GuiSelect,
state: LauncherActivityState.InWorld),
_ => Task.CompletedTask);
Assert.Equal("+alex", row.Character);
Assert.Equal("In game", row.State);
}
[Fact]
public void AProbeIsLabelledAsACharacterRefresh()
{
var row = new LauncherSessionRowViewModel(
Snapshot(kind: LauncherActivityKind.Probe, character: null, mode: null),
_ => Task.CompletedTask);
Assert.Equal("Character refresh", row.Character);
Assert.Equal("Reading characters", row.State);
}
[Fact]
public void StopIsOfferedOnlyWhileTheSessionIsActive()
{
var running = new LauncherSessionRowViewModel(
Snapshot(state: LauncherActivityState.InWorld),
_ => Task.CompletedTask);
var finished = new LauncherSessionRowViewModel(
Snapshot(state: LauncherActivityState.Exited),
_ => Task.CompletedTask);
Assert.True(running.StopCommand.CanExecute(null));
Assert.False(finished.StopCommand.CanExecute(null));
}
private static LauncherSessionSnapshot Snapshot(
LauncherActivityState state = LauncherActivityState.Connected,
LauncherActivityKind kind = LauncherActivityKind.Play,
string? character = "+Acdream",
LaunchMode? mode = LaunchMode.Gui,
string status = "Fixture status.") => new(
"session-1",
kind,
"Local ACE",
"testaccount",
character,
mode,
state,
status,
ExitCode: state == LauncherActivityState.Exited ? 0 : null,
Error: null,
CreatedAt: DateTimeOffset.UnixEpoch);
}