using BlueLaminate.Cli.Tui; using BlueLaminate.Core.Tradeups; using Spectre.Console; using Spectre.Console.Testing; using Xunit; namespace BlueLaminate.Tests.Tui; /// /// Render-path smoke tests for the TUI: the navigation loop needs a live terminal, but the /// rendering (and its Spectre markup) is exercised against a recording console so a stray /// markup tag or unescaped name fails the build instead of crashing on first launch. /// public class TradeupBrowserTests { private static InputListing Input(string name, string market, string? inspect, decimal price) => new(SkinId: 1, MarketHashName: name, Marketplace: market, InspectLink: inspect, ExternalId: "999", FloatValue: 0.1234m, Price: price); private static TradeupCandidate Candidate( string collection = "The Sample Collection", bool statTrak = false, bool guaranteed = true, decimal worstNet = 120m, IReadOnlyList? outcomes = null, IReadOnlyList? inputs = null) { outcomes ??= new[] { new TradeupOutcome(10, "Output Alpha", 0.0672m, WearBand.FactoryNew, 0.5m, 175.75m, 26), new TradeupOutcome(11, "Output Beta", 0.0695m, WearBand.FactoryNew, 0.5m, null, 0), }; inputs ??= new[] { Input("AK-47 | Sample (Field-Tested)", "csfloat", "steam://rungame/730/0/+csgo_econ_action_preview%20ABC123", 1.94m), Input("MP9 | Sample (Minimal Wear)", "csmoney", null, 6.52m), }; return new TradeupCandidate( CollectionId: 1, CollectionName: collection, InputRarity: WeaponRarity.Classified, OutputRarity: WeaponRarity.Covert, StatTrak: statTrak, AverageFraction: 0.0695m, InputCost: 33.16m, ExpectedNet: 120.53m, WorstCaseNet: worstNet, Guaranteed: guaranteed, Inputs: inputs, Outcomes: outcomes, Composition: new[] { new TradeupContribution(1, collection, WeaponRarity.Covert, 10) }); } [Fact] public void RenderDetail_emits_the_key_sections_without_markup_errors() { var console = new TestConsole(); TradeupBrowser.RenderDetail(console, Candidate(), index: 0); var output = console.Output; Assert.Contains("The Sample Collection", output); Assert.Contains("Possible outputs", output); Assert.Contains("Buy list", output); Assert.Contains("Output Alpha", output); Assert.Contains("unpriced", output); // the null-priced output is shown, not dropped } [Theory] [InlineData(false, true, 120)] // guaranteed, positive worst-case (green) [InlineData(true, true, 120)] // StatTrak variant [InlineData(false, false, -40)] // not guaranteed, negative worst-case (red) public void SummaryLine_is_always_valid_markup(bool statTrak, bool guaranteed, int worstNet) { var line = TradeupBrowser.SummaryLine( Candidate(statTrak: statTrak, guaranteed: guaranteed, worstNet: worstNet), index: 3); // The Markup ctor parses the string and throws on a malformed tag. _ = new Markup(line); } [Fact] public void Settings_screen_renders_and_runs_on_enter() { var console = new TestConsole().Interactive(); console.Input.PushKey(ConsoleKey.Enter); // first choice is "Run search" var options = new BlueLaminate.Core.Options.TradeupOptions(); var top = 20; var action = TradeupBrowser.PromptSettings(console, options, ref top); Assert.Equal(SettingsAction.RunSearch, action); Assert.Contains("StatTrak universe", console.Output); Assert.Contains("Run search", console.Output); } [Fact] public void Names_and_links_with_markup_characters_are_escaped() { var console = new TestConsole(); var hostile = Candidate( collection: "Danger [Collection]", outcomes: new[] { new TradeupOutcome(10, "Skin [X]", 0.1m, WearBand.MinimalWear, 1m, 5m, 3) }, inputs: new[] { Input("Weapon [Y] (FT)", "market", inspect: null, price: 1m) }); // Would throw a markup-parse exception if any bracketed field reached Spectre unescaped. TradeupBrowser.RenderDetail(console, hostile, index: 0); Assert.Contains("Danger", console.Output); } }