Skip to content

Commit 097ed78

Browse files
committed
Improve Abstractions coverage
1 parent ef0ebea commit 097ed78

File tree

8 files changed

+389
-55
lines changed

8 files changed

+389
-55
lines changed

Diff for: coverage.runsettings

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
<ModulePaths>
1212
<Include>
1313
<ModulePath>.*\\GqlPlus\.Abstractions\.dll$</ModulePath>
14-
<ModulePath>.*\\GqlPlus\.Generators\.dll$</ModulePath>
14+
<ModulePath>.*\\GqlPlus\.Converter\.Json\.dll$</ModulePath>
15+
<ModulePath>.*\\GqlPlus\.Converter\.Yaml\.dll$</ModulePath>
1516
<ModulePath>.*\\GqlPlus\.Modeller\.dll$</ModulePath>
1617
<ModulePath>.*\\GqlPlus\.Parser\.dll$</ModulePath>
1718
<ModulePath>.*\\GqlPlus\.Verifier\.dll$</ModulePath>

Diff for: src/GqlPlus.Abstractions/GeneralHelpers.cs

+23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System.Diagnostics.CodeAnalysis;
2+
using System.Reflection;
23
using System.Runtime.CompilerServices;
4+
using GqlPlus.Structures;
35

46
namespace GqlPlus;
57

@@ -8,6 +10,27 @@ public static class GeneralHelpers
810
public static TResult[] ArrayOf<TResult>(this IEnumerable<object>? items)
911
=> [.. items?.OfType<TResult>() ?? []];
1012

13+
public static string[]? FlagNames<TEnum>(this TEnum flagValue)
14+
where TEnum : Enum
15+
{
16+
Type type = typeof(TEnum);
17+
if (!type.GetCustomAttributes<FlagsAttribute>().Any()) {
18+
return null;
19+
}
20+
21+
int flags = (int)(object)flagValue;
22+
List<string> result = [];
23+
24+
foreach (object? value in Enum.GetValues(type)) {
25+
int flag = (int)value;
26+
if (flag.IsSingleFlag() && (flags & flag) == flag) {
27+
result.Add(Enum.GetName(type, flag));
28+
}
29+
}
30+
31+
return [.. result];
32+
}
33+
1134
public static string Joined(this IEnumerable<string?>? items, string by = " ")
1235
=> string.Join(by,
1336
items?.Where(i => !string.IsNullOrWhiteSpace(i))

Diff for: src/GqlPlus.Abstractions/Structures/StructureHelper.cs

+3
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ public static Structured RenderEnum<TEnum>(this TEnum value)
5555
internal static bool BothValued<T>([NotNullWhen(true)] this T? left, [NotNullWhen(true)] T? right)
5656
=> left is not null && right is not null;
5757

58+
internal static bool BothAny<T>([NotNullWhen(true)] this IEnumerable<T>? left, [NotNullWhen(true)] IEnumerable<T>? right)
59+
=> left.BothValued(right) && left.Any() && right.Any();
60+
5861
public static bool IsSingleFlag(this int flag)
5962
{
6063
while (flag > 0) {

Diff for: src/GqlPlus.Abstractions/Structures/StructureValue.cs

+10-18
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
namespace GqlPlus.Structures;
22

3-
#pragma warning disable CA1036 // Override methods on comparable types
4-
public sealed record class StructureValue
3+
// #pragma warning disable CA1036 // Override methods on comparable types
4+
public sealed class StructureValue
55
: IComparable<StructureValue>
66
, IEquatable<StructureValue>
77
{
@@ -39,6 +39,7 @@ public int CompareTo(StructureValue? other)
3939
: -1
4040
: -1;
4141

42+
public override bool Equals(object obj) => Equals(obj as StructureValue);
4243
public bool Equals(StructureValue? other)
4344
=> string.Equals(Tag, other?.Tag, StringComparison.Ordinal) && (Boolean.BothValued(other?.Boolean) ? Boolean.Value == other.Boolean
4445
: Number.BothValued(other?.Number) ? Number.Value == other.Number
@@ -51,20 +52,11 @@ public override int GetHashCode()
5152
Identifier?.GetHashCode() ?? 0,
5253
Text?.GetHashCode() ?? 0);
5354

54-
public string AsString
55-
{
56-
get {
57-
if (Identifier is not null) {
58-
return Identifier;
59-
} else if (Boolean is not null) {
60-
return Boolean.Value.TrueFalse();
61-
} else if (Number is not null) {
62-
return $"{Number}";
63-
} else if (Text is not null) {
64-
return Text;
65-
}
66-
67-
return string.Empty;
68-
}
69-
}
55+
public string AsString => this switch {
56+
{ Boolean: not null } => Boolean.Value.TrueFalse(),
57+
{ Identifier: not null } => Identifier,
58+
{ Number: not null } => $"{Number}",
59+
{ Text: not null } => Text,
60+
_ => string.Empty,
61+
};
7062
}

Diff for: src/GqlPlus.Abstractions/Structures/Structured.cs

+14-15
Original file line numberDiff line numberDiff line change
@@ -89,20 +89,16 @@ public Structured AddIf(bool optional, Func<Structured, Structured>? onTrue = nu
8989
public Structured AddSet<TEnum>(string key, TEnum set, string? tag = null, bool flow = true)
9090
where TEnum : Enum
9191
{
92-
Type type = typeof(TEnum);
92+
string[]? flags = set.FlagNames();
9393

94-
if (type.GetCustomAttributes<FlagsAttribute>().Any()) {
95-
int flags = (int)(object)set;
94+
if (flags is not null) {
9695
Dict result = [];
9796

98-
foreach (object? value in Enum.GetValues(type)) {
99-
int flag = (int)value;
100-
if (flag.IsSingleFlag() && (flags & flag) == flag) {
101-
result.Add(new(Enum.GetName(type, value)), new("_"));
102-
}
97+
foreach (string flag in flags) {
98+
result.Add(new(flag), new("_"));
10399
}
104100

105-
return Add(key, new(result, $"_Set({tag ?? type.TypeTag()})", flow: flow));
101+
return Add(key, new(result, $"_Set({tag ?? typeof(TEnum).TypeTag()})", flow: flow));
106102
}
107103

108104
return this;
@@ -111,16 +107,13 @@ public Structured AddSet<TEnum>(string key, TEnum set, string? tag = null, bool
111107
public bool Equals(Structured? other)
112108
=> string.Equals(Tag, other?.Tag, StringComparison.Ordinal)
113109
&& (Value.BothValued(other?.Value) ? Value.Equals(other.Value)
114-
: List.BothValued(other?.List) ? List.SequenceEqual(other.List)
115-
: Map.BothValued(other?.Map) && Map.Equals(other.Map));
110+
: List.BothAny(other?.List) ? List.SequenceEqual(other.List)
111+
: Map.BothAny(other?.Map) && Map.Equals(other.Map));
116112

117113
public override bool Equals(object? obj)
118114
=> Equals(obj as Structured);
119115
public override int GetHashCode()
120-
=> HashCode.Combine(Tag,
121-
Value?.GetHashCode() ?? 0,
122-
List?.GetHashCode() ?? 0,
123-
Map?.GetHashCode() ?? 0);
116+
=> HashCode.Combine(Tag, Value?.GetHashCode() ?? 0, List.GetHashCode(), Map.GetHashCode());
124117
}
125118

126119
#pragma warning disable CA1034 // Nested types should not be visible
@@ -134,10 +127,16 @@ internal sealed class Dict
134127
internal Dict() : base() { }
135128
internal Dict(IDictionary<TValue, TObject> dictionary)
136129
: base(dictionary) { }
130+
131+
public bool Equals(IDict other)
132+
=> other is not null
133+
&& Keys.OrderedEqual(other.Keys)
134+
&& Keys.All(k => this[k].Equals(other[k]));
137135
}
138136

139137
public interface IDict
140138
: IDictionary<TValue, TObject>
139+
, IEquatable<IDict>
141140
{ }
142141

143142
public TValue? Value { get; }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
namespace GqlPlus;
2+
3+
public class GeneralHelpersTests
4+
{
5+
[Fact]
6+
public void ArrayOf_NullInput_ReturnsEmptyArray()
7+
{
8+
IEnumerable<object>? input = null;
9+
10+
string[] result = input.ArrayOf<string>();
11+
12+
result.ShouldSatisfyAllConditions(
13+
r => r.ShouldNotBeNull(),
14+
r => r.ShouldBeEmpty());
15+
}
16+
17+
[Fact]
18+
public void Joined_NullInput_ReturnsEmptyString()
19+
{
20+
IEnumerable<string?>? input = null;
21+
22+
string result = input.Joined();
23+
24+
result.ShouldBe(string.Empty);
25+
}
26+
27+
[Fact]
28+
public void Joined_WithMapping_NullInput_ReturnsEmptyString()
29+
{
30+
IEnumerable<int?>? input = null;
31+
32+
string result = input.Joined(i => $"{i}");
33+
34+
result.ShouldBe(string.Empty);
35+
}
36+
37+
[Fact]
38+
public void ThrowIfNull_NullValue_ThrowsArgumentNullException()
39+
{
40+
string? input = null;
41+
42+
Action result = () => input.ThrowIfNull();
43+
44+
result.ShouldThrow<ArgumentNullException>()
45+
.ParamName.ShouldBe(nameof(input));
46+
}
47+
48+
[Fact]
49+
public void OrderedEqual_NullInput_ThrowsArgumentNullException()
50+
{
51+
IEnumerable<int>? left = null;
52+
IEnumerable<int>? right = null;
53+
54+
Action result = () => left.ThrowIfNull().OrderedEqual(right.ThrowIfNull());
55+
56+
result.ShouldThrow<ArgumentNullException>();
57+
}
58+
59+
[Fact]
60+
public void Prefixed_NullInput_ReturnsEmptyString()
61+
{
62+
string? input = null;
63+
64+
string result = input.Prefixed("prefix");
65+
66+
result.ShouldBe(string.Empty);
67+
}
68+
69+
[Fact]
70+
public void Suffixed_NullInput_ReturnsEmptyString()
71+
{
72+
string? input = null;
73+
74+
string result = input.Suffixed("suffix");
75+
76+
result.ShouldBe(string.Empty);
77+
}
78+
79+
[Fact]
80+
public void Quoted_NullInput_ReturnsEmptyString()
81+
{
82+
string? input = null;
83+
84+
string result = input.Quoted("\"");
85+
86+
result.ShouldBe(string.Empty);
87+
}
88+
89+
[Fact]
90+
public void Surround_NullInput_ReturnsEmptyStringWithSurrounding()
91+
{
92+
IEnumerable<string>? input = null;
93+
94+
string result = input.Surround("[", "]");
95+
96+
result.ShouldBe("[]");
97+
}
98+
99+
[Fact]
100+
public void Surround_WithMapping_NullInput_ReturnsEmptyStringWithSurrounding()
101+
{
102+
IEnumerable<int>? input = null;
103+
104+
string result = input.Surround("[", "]", i => $"{i}");
105+
106+
result.ShouldBe("[]");
107+
}
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+

2+
namespace GqlPlus.Structures;
3+
4+
internal sealed class RenderMap
5+
: IRenderer<Map<string>>
6+
{
7+
public Structured Render(Map<string> model)
8+
=> model.Render(s => new(s));
9+
}

0 commit comments

Comments
 (0)