using Godosa.Core.Runs.Scripts; using Xunit; namespace Godosa.Core.Tests.Runs.Scripts; public class VarsTests { private static readonly Dictionary Values = new() { ["res"] = "1920x1080", ["hours"] = "8", ["ui.rest.8"] = "400,300", ["a"] = "b", ["b"] = "deep", }; private static string Expand(string text) => VarExpander.Expand(text, n => Values.GetValueOrDefault(n), p => p == "global(gold)" ? RunValue.Of(1250) : throw new ScriptFault($"no probe {p}")); [Theory] [InlineData("ui/${res}.vars", "ui/1920x1080.vars")] [InlineData("click ${ui.rest.${hours}}", "click 400,300")] [InlineData("${${a}}", "deep")] [InlineData("gold ${probe:global(gold)}", "gold 1250")] [InlineData("cost $5 and $", "cost $5 and $")] // review focus 4: lone $ is literal [InlineData("no vars", "no vars")] public void Expand_Values(string text, string expected) => Assert.Equal(expected, Expand(text)); [Theory] [InlineData("${missing}", "unresolved variable ${missing}")] [InlineData("x ${a", "unclosed ${ in 'x ${a'")] [InlineData("${}", "empty ${}")] public void Expand_Errors_AreFaults(string text, string message) => Assert.Equal(message, Assert.Throws(() => Expand(text)).Message); [Fact] public void Names_InnermostPlainOnly() => Assert.Equal(["hours", "res"], VarExpander.Names("${ui.${hours}} ${res} ${probe:quest(1)}").Order()); [Fact] public void VarsFile_ParsesAndReportsBadLines() { var (values, errors) = VarsFile.Parse("ui/hd.vars", "# hd\nui.answer1 = 412,520\n\nui.rest.8=400,300\nbroken line\n"); Assert.Equal("412,520", values["ui.answer1"]); Assert.Equal("400,300", values["ui.rest.8"]); Assert.Equal(["ui/hd.vars:5: bad vars line (name = value)"], errors.Select(e => e.ToString())); } [Fact] public void Scopes_SetGoesToLocalOrRoot() { var s = new VarScopes(OutputLevel.Labels); s.Set("g", "1"); s.Push(local: false, OutputLevel.Labels, []); // include s.Set("inc", "2"); // global: root s.Push(local: true, OutputLevel.Labels, [new("p", "x")]); // def call s.Set("loc", "3"); Assert.Equal(("1", "2", "3", "x"), (s.Get("g"), s.Get("inc"), s.Get("loc"), s.Get("p"))); s.Pop(); Assert.Null(s.Get("loc")); Assert.Null(s.Get("p")); s.Pop(); Assert.Equal("2", s.Get("inc")); } [Fact] public void Scopes_OutputLevelRestoredOnPop() { var s = new VarScopes(OutputLevel.Labels); s.Push(local: false, s.Level, []); s.Level = OutputLevel.Trace; s.Pop(); Assert.Equal(OutputLevel.Labels, s.Level); } [Fact] public void Scopes_SnapshotRestore_RoundTrips() { var s = new VarScopes(OutputLevel.Labels); s.Set("g", "1"); s.Push(local: true, OutputLevel.Steps, [new("p", "x")]); var copy = new VarScopes(OutputLevel.Quiet); copy.Restore(s.Snapshot()); Assert.Equal(("1", "x", OutputLevel.Steps), (copy.Get("g"), copy.Get("p"), copy.Level)); s.Set("p", "changed"); Assert.Equal("x", copy.Get("p")); // deep copy } }