using Microsoft.AspNetCore.Components; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Raw; /// /// R04 H2: the modals' DynamicComponent parameter dictionaries are reused across renders while their /// values are unchanged, and rebuilt — carrying the current value — the moment one changes. The second /// half is the regression a naive build-once cache would introduce (stale JSON stranding every edit). /// [Trait("Category", "Unit")] public sealed class ParameterDictionaryCacheTests { [Fact] public void Unchanged_values_return_the_same_dictionary() { var cache = new ParameterDictionaryCache(); var builds = 0; var json = "{}"; Dictionary Build() { builds++; return new() { ["Json"] = json }; } var first = cache.Get([json], Build); cache.Get([json], Build).ShouldBeSameAs(first); builds.ShouldBe(1); } [Fact] public void A_changed_value_rebuilds_with_the_current_value() { var cache = new ParameterDictionaryCache(); var json = "{}"; Dictionary Build() => new() { ["Json"] = json }; cache.Get([json], Build)["Json"].ShouldBe("{}"); json = "{\"a\":1}"; cache.Get([json], Build)["Json"].ShouldBe("{\"a\":1}"); } [Fact] public void Null_values_compare_and_reset_forces_a_rebuild() { var cache = new ParameterDictionaryCache(); var builds = 0; Dictionary Build() { builds++; return new(); } cache.Get(["x", null], Build); cache.Get(["x", null], Build); builds.ShouldBe(1); cache.Reset(); cache.Get(["x", null], Build); builds.ShouldBe(2); } [Fact] public void Modals_build_their_parameters_through_the_cache_with_reused_callbacks() { foreach (var (path, cacheField) in new[] { (new[] { "Components", "Shared", "Drivers", "DriverConfigModal.razor" }, "_formParameterCache.Get("), (new[] { "Components", "Shared", "Drivers", "DeviceModal.razor" }, "_deviceFormParameterCache.Get("), (new[] { "Components", "Shared", "Raw", "RawTagModal.razor" }, "_editorParameterCache.Get("), }) { var src = AdminUiSource.Read(path); src.ShouldContain(cacheField, customMessage: path[^1]); // Every EventCallback is created behind a ??= so it is made once per component. var all = System.Text.RegularExpressions.Regex.Matches(src, @"EventCallback\.Factory\.Create").Count; var once = System.Text.RegularExpressions.Regex.Matches(src, @"\?\?=\s*EventCallback\.Factory\.Create").Count; all.ShouldBeGreaterThan(0, path[^1]); once.ShouldBe(all, path[^1]); } } [Fact] public void RawTagModal_reuses_its_callback_across_renders() { var modal = new ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Raw.RawTagModal(); var first = modal.BuildEditorParameters(); var second = modal.BuildEditorParameters(); second.ShouldBeSameAs(first); ((EventCallback)first["ConfigJsonChanged"]).HasDelegate.ShouldBeTrue(); } }