diff --git a/src/GameCult.Caching/CultInspectorModel.cs b/src/GameCult.Caching/CultInspectorModel.cs index 34b1faf9..7c3cb45e 100644 --- a/src/GameCult.Caching/CultInspectorModel.cs +++ b/src/GameCult.Caching/CultInspectorModel.cs @@ -675,11 +675,11 @@ namespace GameCult.Caching return new GroupLevel(identity, label, CreateRecordRef(member.ValueType, identity), rank, 0m, label, identity); case CultInspectorValueKind.Enum: var number = Convert.ToDecimal(Convert.ChangeType(value, Enum.GetUnderlyingType(member.ValueType)), System.Globalization.CultureInfo.InvariantCulture); - return new GroupLevel(identity, Enum.GetName(member.ValueType, value!) ?? identity, value, 1, number, string.Empty, string.Empty); + return new GroupLevel(identity, Enum.GetName(member.ValueType, value!) ?? identity, value, 1, number); case CultInspectorValueKind.Integer: - return new GroupLevel(identity, identity, value, 1, Convert.ToDecimal(value, System.Globalization.CultureInfo.InvariantCulture), string.Empty, string.Empty); + return new GroupLevel(identity, identity, value, 1, Convert.ToDecimal(value, System.Globalization.CultureInfo.InvariantCulture)); case CultInspectorValueKind.Bool: - return new GroupLevel(identity, identity, value, 1, (bool)value! ? 1m : 0m, string.Empty, string.Empty); + return new GroupLevel(identity, identity, value, 1, (bool)value! ? 1m : 0m); default: return new GroupLevel(identity, identity.Length == 0 ? "(empty)" : identity, identity, 1, 0m, identity, identity); } @@ -687,7 +687,7 @@ namespace GameCult.Caching private sealed class GroupLevel { - public GroupLevel(string identity, string label, object? value, int rank, decimal number, string text, string tie) + public GroupLevel(string identity, string label, object? value, int rank, decimal number, string text = "", string tie = "") { Identity = identity; Label = label; diff --git a/tests/GameCult.Caching.Tests/CultInspectorModelTests.cs b/tests/GameCult.Caching.Tests/CultInspectorModelTests.cs index bee96e15..3432c858 100644 --- a/tests/GameCult.Caching.Tests/CultInspectorModelTests.cs +++ b/tests/GameCult.Caching.Tests/CultInspectorModelTests.cs @@ -519,6 +519,8 @@ namespace GameCult.Caching.Tests using var cache = OpenGroups(); await cache.UpsertAsync(typeof(GroupMixed), new GroupMixed { Kind = "x/Count=1/Flag=True" }, new CultRecordKey("a")); await cache.UpsertAsync(typeof(GroupMixed), new GroupMixed { Kind = "x", Count = 1, Flag = true }, new CultRecordKey("b")); + await cache.UpsertAsync(typeof(GroupMixed), new GroupMixed { Kind = "aCount0" }, new CultRecordKey("c")); + await cache.UpsertAsync(typeof(GroupMixed), new GroupMixed { Kind = "a" }, new CultRecordKey("d")); var ids = new List(); void Collect(IEnumerable groups) @@ -532,8 +534,8 @@ namespace GameCult.Caching.Tests Collect(GroupModel().GroupRecords(typeof(GroupMixed), cache.AllStoredDocuments)); - Assert.That(ids, Has.Count.EqualTo(6)); - Assert.That(ids.Distinct().Count(), Is.EqualTo(6), "a path never spells another node's id"); + Assert.That(ids, Has.Count.EqualTo(12)); + Assert.That(ids.Distinct().Count(), Is.EqualTo(12), "a path never spells another node's id"); } [Test] @@ -593,6 +595,33 @@ namespace GameCult.Caching.Tests Assert.That(notice, Does.Contain("parameterless constructor")); } + [Test] + public void GroupATypeWithoutADeclarationIsNotGrouped() + { + var model = GroupModel(); + + var grouping = model.GroupingOf(typeof(InspectOther)); + + Assert.That(grouping.ListedType, Is.EqualTo(typeof(InspectOther))); + Assert.That(grouping.Members, Is.Empty); + Assert.That(grouping.Notice, Is.Null); + Assert.That(model.GroupRecords(typeof(InspectOther), Array.Empty()), Is.Empty); + Assert.That(() => model.GroupingOf(null!), Throws.ArgumentNullException); + } + + [Test] + public async Task GroupANullStringSharesTheEmptyStringsNode() + { + using var cache = OpenGroups(); + await cache.UpsertAsync(typeof(GroupMixed), new GroupMixed { Kind = null! }, new CultRecordKey("n")); + await cache.UpsertAsync(typeof(GroupMixed), new GroupMixed { Kind = "" }, new CultRecordKey("e")); + + var roots = GroupModel().GroupRecords(typeof(GroupMixed), cache.AllStoredDocuments); + + Assert.That(roots.Select(group => group.Label), Is.EqualTo(new[] { "(empty)" })); + Assert.That(roots.Single().Count, Is.EqualTo(2)); + } + [TestCase(typeof(GroupUnknown), "Nope")] [TestCase(typeof(GroupFloat), "Mass")] [TestCase(typeof(GroupFlagsEnum), "Traits")] @@ -601,6 +630,7 @@ namespace GameCult.Caching.Tests [TestCase(typeof(GroupDuplicate), "Kind")] [TestCase(typeof(GroupValidThenInvalid), "Mass")] [TestCase(typeof(GroupGlobal), "global")] + [TestCase(typeof(GroupGlobalChild), "global")] public void GroupInvalidDeclarationsGiveANoticeAndAFlatList(Type listed, string mentions) { var model = GroupModel(); @@ -858,12 +888,17 @@ namespace GameCult.Caching.Tests [CultGlobal] [CultDocument("tests.group_global", "tests.group_global.v1")] [MessagePackObject] - public sealed class GroupGlobal + public class GroupGlobal { [Key(0)] public string Kind = string.Empty; } + [CultInspectorGroupBy("Kind")] + public sealed class GroupGlobalChild : GroupGlobal + { + } + [CultInspectorGroupBy("Kind")] [CultDocument("tests.group_no_ctor", "tests.group_no_ctor.v1")] [MessagePackObject]