From 3c42f58242bcdfa97b8dbf411c0f80f6069900a4 Mon Sep 17 00:00:00 2001 From: Andreas Gullberg Larsen Date: Thu, 23 Jul 2026 00:38:18 +0200 Subject: [PATCH 1/2] Add quantity selection factories Extracted from angularsen/UnitsNet#1544 (https://github.com/angularsen/UnitsNet/pull/1544) because composing an isolated quantity catalog is useful for custom quantities without depending on QuantityValue. Moving this API out gives the catalog design focused review and reduces the feature PR's size and initialization surface. Changes: - Add QuantitiesSelector for appending external quantity definitions to a base catalog. - Add configured factory overloads for QuantityInfoLookup, UnitAbbreviationsCache, and UnitParser. - Allow parser factories to configure abbreviations before parser construction. - Add UnitsNetSetup.Create and a selection builder for isolated, consistently wired setups. - Keep UnitsNetSetup.Default behavior unchanged. Tests: - Add QuantitiesSelector coverage for base, chained additions, ordering, and null input. - Add cache factory coverage for default and custom base catalogs. - Add parser factory coverage for external quantities and custom abbreviations. - Add isolated setup coverage for component wiring, catalog selection, additions, and duplicate base selection. - Verify all affected tests on net10.0 and net48 (118 tests per target). --- .../Builder/QuantitiesSelectorTests.cs | 33 +++++++ UnitsNet.Tests/UnitAbbreviationsCacheTests.cs | 20 ++++ UnitsNet.Tests/UnitParserTests.cs | 17 ++++ UnitsNet.Tests/UnitsNetSetupTests.cs | 40 ++++++++ .../Builder/QuantitiesSelector.cs | 39 ++++++++ UnitsNet/CustomCode/UnitAbbreviationsCache.cs | 21 +++++ UnitsNet/CustomCode/UnitParser.cs | 49 ++++++++++ UnitsNet/CustomCode/UnitsNetSetup.cs | 92 +++++++++++++++++++ UnitsNet/QuantityInfoLookup.cs | 10 ++ 9 files changed, 321 insertions(+) create mode 100644 UnitsNet.Tests/CustomCode/QuantityInfos/Builder/QuantitiesSelectorTests.cs create mode 100644 UnitsNet.Tests/UnitsNetSetupTests.cs create mode 100644 UnitsNet/CustomCode/QuantityInfo/Builder/QuantitiesSelector.cs diff --git a/UnitsNet.Tests/CustomCode/QuantityInfos/Builder/QuantitiesSelectorTests.cs b/UnitsNet.Tests/CustomCode/QuantityInfos/Builder/QuantitiesSelectorTests.cs new file mode 100644 index 0000000000..78fc005fcd --- /dev/null +++ b/UnitsNet.Tests/CustomCode/QuantityInfos/Builder/QuantitiesSelectorTests.cs @@ -0,0 +1,33 @@ +// Licensed under MIT No Attribution, see LICENSE file at the root. +// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. + +namespace UnitsNet.Tests.QuantityInfos.Builder; + +public class QuantitiesSelectorTests +{ + [Fact] + public void GetQuantityInfos_WithoutAdditions_ReturnsBaseSelection() + { + var selector = new QuantitiesSelector(() => [Length.Info]); + + Assert.Equal([Length.Info], selector.GetQuantityInfos()); + } + + [Fact] + public void WithAdditionalQuantities_AppendsAllSelectionsInOrder() + { + QuantitiesSelector selector = new QuantitiesSelector(() => [Length.Info]) + .WithAdditionalQuantities([Mass.Info]) + .WithAdditionalQuantities([Duration.Info]); + + Assert.Equal([Length.Info, Mass.Info, Duration.Info], selector.GetQuantityInfos()); + } + + [Fact] + public void WithAdditionalQuantities_WithNull_ThrowsArgumentNullException() + { + var selector = new QuantitiesSelector(() => [Length.Info]); + + Assert.Throws(() => selector.WithAdditionalQuantities(null!)); + } +} diff --git a/UnitsNet.Tests/UnitAbbreviationsCacheTests.cs b/UnitsNet.Tests/UnitAbbreviationsCacheTests.cs index 9b45d8b990..a2d9d53fd7 100644 --- a/UnitsNet.Tests/UnitAbbreviationsCacheTests.cs +++ b/UnitsNet.Tests/UnitAbbreviationsCacheTests.cs @@ -49,6 +49,26 @@ public void CreateDefault_ReturnsAnAbbreviationCacheWithDefaultQuantityInfoLooku Assert.Throws(() => unitAbbreviationCache.GetUnitAbbreviations(HowMuchUnit.Some)); } + [Fact] + public void CreateDefault_WithAdditionalQuantity_ReturnsCacheWithExtendedQuantityInfoLookup() + { + var unitAbbreviationCache = UnitAbbreviationsCache.CreateDefault(selector => selector.WithAdditionalQuantities([HowMuch.Info])); + + Assert.NotEqual(UnitsNetSetup.Default.Quantities, unitAbbreviationCache.Quantities); + Assert.Equal("g", unitAbbreviationCache.GetUnitAbbreviations(MassUnit.Gram, AmericanCulture)[0]); + Assert.Empty(unitAbbreviationCache.GetUnitAbbreviations(HowMuchUnit.Some, AmericanCulture)); + } + + [Fact] + public void Create_WithBaseAndAdditionalQuantities_ReturnsConfiguredCache() + { + var unitAbbreviationCache = UnitAbbreviationsCache.Create([Mass.Info], + selector => selector.WithAdditionalQuantities([HowMuch.Info])); + + Assert.Equal([Mass.Info, HowMuch.Info], unitAbbreviationCache.Quantities.Infos); + Assert.Throws(() => unitAbbreviationCache.GetUnitAbbreviations(LengthUnit.Meter)); + } + [Fact] public void UnitAbbreviationsCache_Default_ReturnsInstanceFromUnitsNetSetup() { diff --git a/UnitsNet.Tests/UnitParserTests.cs b/UnitsNet.Tests/UnitParserTests.cs index e168f08aeb..f8a87a2262 100644 --- a/UnitsNet.Tests/UnitParserTests.cs +++ b/UnitsNet.Tests/UnitParserTests.cs @@ -35,6 +35,23 @@ public void CreateDefault_CreatesNewAbbreviationsCacheWithDefaultQuantities() Assert.NotEqual(UnitAbbreviationsCache.Default, unitParser.Abbreviations); Assert.Equal(UnitsNetSetup.Default.Quantities, unitParser.Quantities); } + + [Fact] + public void CreateDefault_WithAdditionalQuantity_CreatesParserWithExtendedCatalog() + { + var unitParser = UnitParser.CreateDefault(selector => selector.WithAdditionalQuantities([HowMuch.Info])); + + Assert.Contains(HowMuch.Info, unitParser.Quantities.Infos); + } + + [Fact] + public void Create_WithConfiguredAbbreviation_ParsesExternalQuantityUnit() + { + var unitParser = UnitParser.Create([HowMuch.Info], _ => { }, + abbreviations => abbreviations.MapUnitToAbbreviation(HowMuchUnit.Some, "some")); + + Assert.Equal(HowMuchUnit.Some, unitParser.Parse("some")); + } [Theory] [InlineData("m^^2", AreaUnit.SquareMeter)] diff --git a/UnitsNet.Tests/UnitsNetSetupTests.cs b/UnitsNet.Tests/UnitsNetSetupTests.cs new file mode 100644 index 0000000000..e8b6ac45d1 --- /dev/null +++ b/UnitsNet.Tests/UnitsNetSetupTests.cs @@ -0,0 +1,40 @@ +// Licensed under MIT No Attribution, see LICENSE file at the root. +// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. + +using UnitsNet.Tests.CustomQuantities; +using UnitsNet.Units; + +namespace UnitsNet.Tests; + +public class UnitsNetSetupTests +{ + [Fact] + public void Create_WithSelectedQuantities_WiresIsolatedComponentsToSameCatalog() + { + UnitsNetSetup setup = UnitsNetSetup.Create(builder => builder.WithQuantities([Mass.Info])); + + Assert.Equal([Mass.Info], setup.Quantities.Infos); + Assert.Same(setup.Quantities, setup.UnitAbbreviations.Quantities); + Assert.Same(setup.UnitAbbreviations, setup.UnitParser.Abbreviations); + Assert.NotSame(UnitsNetSetup.Default, setup); + Assert.Throws(() => setup.Quantities.GetQuantityByUnitType(typeof(LengthUnit))); + } + + [Fact] + public void Create_WithAdditionalQuantities_AppendsToSelectedCatalog() + { + UnitsNetSetup setup = UnitsNetSetup.Create(builder => builder + .WithQuantities([Mass.Info]) + .WithAdditionalQuantities([HowMuch.Info])); + + Assert.Equal([Mass.Info, HowMuch.Info], setup.Quantities.Infos); + } + + [Fact] + public void Builder_WithQuantitiesTwice_ThrowsInvalidOperationException() + { + Assert.Throws(() => UnitsNetSetup.Create(builder => builder + .WithQuantities([Mass.Info]) + .WithQuantities([Length.Info]))); + } +} diff --git a/UnitsNet/CustomCode/QuantityInfo/Builder/QuantitiesSelector.cs b/UnitsNet/CustomCode/QuantityInfo/Builder/QuantitiesSelector.cs new file mode 100644 index 0000000000..d8332802c1 --- /dev/null +++ b/UnitsNet/CustomCode/QuantityInfo/Builder/QuantitiesSelector.cs @@ -0,0 +1,39 @@ +// Licensed under MIT No Attribution, see LICENSE file at the root. +// Copyright 2013 Andreas Gullberg Larsen (andreas.larsen84@gmail.com). Maintained at https://github.com/angularsen/UnitsNet. + +using System.Linq; + +namespace UnitsNet; + +/// +/// Selects the quantities used to construct an isolated UnitsNet component or setup. +/// +public sealed class QuantitiesSelector +{ + private readonly Func> _defaultQuantities; + private IEnumerable? _additionalQuantities; + + internal QuantitiesSelector(Func> defaultQuantities) + { + _defaultQuantities = defaultQuantities ?? throw new ArgumentNullException(nameof(defaultQuantities)); + } + + /// + /// Appends external quantity definitions to the current selection. + /// + /// The quantity definitions to append. + /// This selector, for method chaining. + public QuantitiesSelector WithAdditionalQuantities(IEnumerable quantities) + { + if (quantities is null) throw new ArgumentNullException(nameof(quantities)); + + _additionalQuantities = _additionalQuantities?.Concat(quantities) ?? quantities; + return this; + } + + internal IEnumerable GetQuantityInfos() + { + IEnumerable quantities = _defaultQuantities(); + return _additionalQuantities is null ? quantities : quantities.Concat(_additionalQuantities); + } +} diff --git a/UnitsNet/CustomCode/UnitAbbreviationsCache.cs b/UnitsNet/CustomCode/UnitAbbreviationsCache.cs index af397e9d35..0a023f5d06 100644 --- a/UnitsNet/CustomCode/UnitAbbreviationsCache.cs +++ b/UnitsNet/CustomCode/UnitAbbreviationsCache.cs @@ -85,6 +85,27 @@ public static UnitAbbreviationsCache CreateDefault() return new UnitAbbreviationsCache(); } + /// + /// Creates a cache for the built-in quantities and any additions made by . + /// + /// Configures the selected quantities. + /// A cache mapped to the configured quantity selection. + public static UnitAbbreviationsCache CreateDefault(Action configureQuantities) + { + return Create(Quantity.DefaultProvider.Quantities, configureQuantities); + } + + /// + /// Creates a cache for a configured selection based on . + /// + /// The initial quantity definitions. + /// Configures the selected quantities. + /// A cache mapped to the configured quantity selection. + public static UnitAbbreviationsCache Create(IEnumerable defaultQuantities, Action configureQuantities) + { + return new UnitAbbreviationsCache(QuantityInfoLookup.Create(defaultQuantities, configureQuantities)); + } + #region MapUnitToAbbreviation overloads /// diff --git a/UnitsNet/CustomCode/UnitParser.cs b/UnitsNet/CustomCode/UnitParser.cs index b403d86782..21985ce6ff 100644 --- a/UnitsNet/CustomCode/UnitParser.cs +++ b/UnitsNet/CustomCode/UnitParser.cs @@ -82,6 +82,55 @@ public static UnitParser CreateDefault() return new UnitParser(UnitAbbreviationsCache.CreateDefault()); } + /// + /// Creates a parser for the built-in quantities and any additions made by . + /// + /// Configures the selected quantities. + /// A parser for the configured quantity selection. + public static UnitParser CreateDefault(Action configureQuantities) + { + return new UnitParser(UnitAbbreviationsCache.CreateDefault(configureQuantities)); + } + + /// + /// Creates a parser for the built-in quantities, with configurable quantities and abbreviations. + /// + /// Configures the selected quantities. + /// Configures abbreviations before constructing the parser. + /// A parser for the configured quantities and abbreviations. + public static UnitParser CreateDefault(Action configureQuantities, Action configureAbbreviations) + { + return Create(Quantity.DefaultProvider.Quantities, configureQuantities, configureAbbreviations); + } + + /// + /// Creates a parser for a configured selection based on . + /// + /// The initial quantity definitions. + /// Configures the selected quantities. + /// A parser for the configured quantity selection. + public static UnitParser Create(IEnumerable defaultQuantities, Action configureQuantities) + { + return new UnitParser(UnitAbbreviationsCache.Create(defaultQuantities, configureQuantities)); + } + + /// + /// Creates a parser for a base catalog, with configurable quantities and abbreviations. + /// + /// The initial quantity definitions. + /// Configures the selected quantities. + /// Configures abbreviations before constructing the parser. + /// A parser for the configured quantities and abbreviations. + public static UnitParser Create(IEnumerable defaultQuantities, Action configureQuantities, + Action configureAbbreviations) + { + if (configureAbbreviations is null) throw new ArgumentNullException(nameof(configureAbbreviations)); + + var unitAbbreviations = UnitAbbreviationsCache.Create(defaultQuantities, configureQuantities); + configureAbbreviations(unitAbbreviations); + return new UnitParser(unitAbbreviations); + } + /// /// Parses a unit abbreviation for a given unit enumeration type. /// Example: Parse<LengthUnit>("km") => LengthUnit.Kilometer diff --git a/UnitsNet/CustomCode/UnitsNetSetup.cs b/UnitsNet/CustomCode/UnitsNetSetup.cs index e524434b5d..4e20412cef 100644 --- a/UnitsNet/CustomCode/UnitsNetSetup.cs +++ b/UnitsNet/CustomCode/UnitsNetSetup.cs @@ -14,6 +14,84 @@ namespace UnitsNet; /// public sealed class UnitsNetSetup { + /// + /// Builds a UnitsNet setup by selecting built-in or external quantity definitions. + /// + public sealed class DefaultConfigurationBuilder + { + private QuantitiesSelector? _quantitiesSelector; + + /// + /// Uses the specified quantities as the setup's base catalog. + /// + /// The quantity definitions to use. + /// This builder, for method chaining. + public DefaultConfigurationBuilder WithQuantities(IEnumerable quantities) + { + if (quantities is null) throw new ArgumentNullException(nameof(quantities)); + return WithQuantities(() => quantities); + } + + /// + /// Uses quantities returned by as the setup's base catalog. + /// + /// Provides the quantity definitions to use. + /// This builder, for method chaining. + public DefaultConfigurationBuilder WithQuantities(Func> quantities) + { + if (quantities is null) throw new ArgumentNullException(nameof(quantities)); + if (_quantitiesSelector is not null) throw new InvalidOperationException("The base quantity selection is already configured."); + + _quantitiesSelector = new QuantitiesSelector(quantities); + return this; + } + + /// + /// Uses the specified quantities as the setup's base catalog and configures that selection. + /// + /// The quantity definitions to use. + /// Configures the selected quantities. + /// This builder, for method chaining. + public DefaultConfigurationBuilder WithQuantities(IEnumerable quantities, Action configureQuantities) + { + if (quantities is null) throw new ArgumentNullException(nameof(quantities)); + return WithQuantities(() => quantities, configureQuantities); + } + + /// + /// Uses quantities returned by as the setup's base catalog and configures that selection. + /// + /// Provides the quantity definitions to use. + /// Configures the selected quantities. + /// This builder, for method chaining. + public DefaultConfigurationBuilder WithQuantities(Func> quantities, Action configureQuantities) + { + if (configureQuantities is null) throw new ArgumentNullException(nameof(configureQuantities)); + + WithQuantities(quantities); + configureQuantities(_quantitiesSelector!); + return this; + } + + /// + /// Appends external quantity definitions to the current selection. + /// + /// The quantity definitions to append. + /// This builder, for method chaining. + public DefaultConfigurationBuilder WithAdditionalQuantities(IEnumerable quantities) + { + _quantitiesSelector ??= new QuantitiesSelector(() => Quantity.DefaultProvider.Quantities); + _quantitiesSelector.WithAdditionalQuantities(quantities); + return this; + } + + internal UnitsNetSetup Build() + { + IEnumerable quantities = _quantitiesSelector?.GetQuantityInfos() ?? Quantity.DefaultProvider.Quantities; + return new UnitsNetSetup(quantities, UnitConverter.CreateDefault()); + } + } + static UnitsNetSetup() { IReadOnlyCollection quantityInfos = Quantity.DefaultProvider.Quantities; @@ -24,6 +102,20 @@ static UnitsNetSetup() Default = new UnitsNetSetup(quantityInfos, unitConverter); } + /// + /// Creates an isolated setup without changing . + /// + /// Configures the quantities included in the setup. + /// A configured UnitsNet setup. + public static UnitsNetSetup Create(Action configuration) + { + if (configuration is null) throw new ArgumentNullException(nameof(configuration)); + + var builder = new DefaultConfigurationBuilder(); + configuration(builder); + return builder.Build(); + } + /// /// Create a new UnitsNet setup with the given quantities, their units and unit conversion functions between units. /// diff --git a/UnitsNet/QuantityInfoLookup.cs b/UnitsNet/QuantityInfoLookup.cs index 5aa7843f93..ce788a39e9 100644 --- a/UnitsNet/QuantityInfoLookup.cs +++ b/UnitsNet/QuantityInfoLookup.cs @@ -99,6 +99,16 @@ public QuantityInfoLookup(IEnumerable quantityInfos) /// public IReadOnlyList Infos => _quantities; + internal static QuantityInfoLookup Create(IEnumerable defaultQuantities, Action configureQuantities) + { + if (defaultQuantities is null) throw new ArgumentNullException(nameof(defaultQuantities)); + if (configureQuantities is null) throw new ArgumentNullException(nameof(configureQuantities)); + + var selector = new QuantitiesSelector(() => defaultQuantities); + configureQuantities(selector); + return new QuantityInfoLookup(selector.GetQuantityInfos()); + } + /// /// Retrieves the associated with the specified quantity type. /// From f15fac7f73caa04b5d2f59673867bace29591a88 Mon Sep 17 00:00:00 2001 From: Andreas Gullberg Larsen Date: Thu, 23 Jul 2026 01:57:33 +0200 Subject: [PATCH 2/2] Test quantity selection factory overloads Cover the setup and parser factory overloads missed by Codecov, and verify null argument contracts across the new public factory APIs. --- UnitsNet.Tests/UnitAbbreviationsCacheTests.cs | 14 +++++++ UnitsNet.Tests/UnitParserTests.cs | 37 +++++++++++++++++++ UnitsNet.Tests/UnitsNetSetupTests.cs | 29 +++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/UnitsNet.Tests/UnitAbbreviationsCacheTests.cs b/UnitsNet.Tests/UnitAbbreviationsCacheTests.cs index a2d9d53fd7..1e68f6c185 100644 --- a/UnitsNet.Tests/UnitAbbreviationsCacheTests.cs +++ b/UnitsNet.Tests/UnitAbbreviationsCacheTests.cs @@ -69,6 +69,20 @@ public void Create_WithBaseAndAdditionalQuantities_ReturnsConfiguredCache() Assert.Throws(() => unitAbbreviationCache.GetUnitAbbreviations(LengthUnit.Meter)); } + [Fact] + public void FactoryMethods_WithNullArguments_ThrowArgumentNullException() + { + Assert.Multiple(checks: + [ + () => Assert.Equal("configureQuantities", + Assert.Throws(() => UnitAbbreviationsCache.CreateDefault(null!)).ParamName), + () => Assert.Equal("defaultQuantities", + Assert.Throws(() => UnitAbbreviationsCache.Create(null!, _ => { })).ParamName), + () => Assert.Equal("configureQuantities", + Assert.Throws(() => UnitAbbreviationsCache.Create([Mass.Info], null!)).ParamName) + ]); + } + [Fact] public void UnitAbbreviationsCache_Default_ReturnsInstanceFromUnitsNetSetup() { diff --git a/UnitsNet.Tests/UnitParserTests.cs b/UnitsNet.Tests/UnitParserTests.cs index f8a87a2262..0c9c06a158 100644 --- a/UnitsNet.Tests/UnitParserTests.cs +++ b/UnitsNet.Tests/UnitParserTests.cs @@ -44,6 +44,25 @@ public void CreateDefault_WithAdditionalQuantity_CreatesParserWithExtendedCatalo Assert.Contains(HowMuch.Info, unitParser.Quantities.Infos); } + [Fact] + public void CreateDefault_WithConfiguredQuantityAndAbbreviation_ParsesExternalQuantityUnit() + { + var unitParser = UnitParser.CreateDefault( + selector => selector.WithAdditionalQuantities([HowMuch.Info]), + abbreviations => abbreviations.MapUnitToAbbreviation(HowMuchUnit.Some, "some")); + + Assert.Equal(HowMuchUnit.Some, unitParser.Parse("some")); + } + + [Fact] + public void Create_WithBaseAndAdditionalQuantities_CreatesParserWithSelectedCatalog() + { + var unitParser = UnitParser.Create([Mass.Info], + selector => selector.WithAdditionalQuantities([HowMuch.Info])); + + Assert.Equal([Mass.Info, HowMuch.Info], unitParser.Quantities.Infos); + } + [Fact] public void Create_WithConfiguredAbbreviation_ParsesExternalQuantityUnit() { @@ -52,6 +71,24 @@ public void Create_WithConfiguredAbbreviation_ParsesExternalQuantityUnit() Assert.Equal(HowMuchUnit.Some, unitParser.Parse("some")); } + + [Fact] + public void FactoryMethods_WithNullArguments_ThrowArgumentNullException() + { + Assert.Multiple(checks: + [ + () => Assert.Equal("configureQuantities", + Assert.Throws(() => UnitParser.CreateDefault((Action)null!)).ParamName), + () => Assert.Equal("configureAbbreviations", + Assert.Throws(() => UnitParser.CreateDefault(_ => { }, null!)).ParamName), + () => Assert.Equal("defaultQuantities", + Assert.Throws(() => UnitParser.Create(null!, _ => { })).ParamName), + () => Assert.Equal("configureQuantities", + Assert.Throws(() => UnitParser.Create([Mass.Info], null!)).ParamName), + () => Assert.Equal("configureAbbreviations", + Assert.Throws(() => UnitParser.Create([Mass.Info], _ => { }, null!)).ParamName) + ]); + } [Theory] [InlineData("m^^2", AreaUnit.SquareMeter)] diff --git a/UnitsNet.Tests/UnitsNetSetupTests.cs b/UnitsNet.Tests/UnitsNetSetupTests.cs index e8b6ac45d1..cc7364b019 100644 --- a/UnitsNet.Tests/UnitsNetSetupTests.cs +++ b/UnitsNet.Tests/UnitsNetSetupTests.cs @@ -30,6 +30,35 @@ public void Create_WithAdditionalQuantities_AppendsToSelectedCatalog() Assert.Equal([Mass.Info, HowMuch.Info], setup.Quantities.Infos); } + [Fact] + public void Create_WithSelectedAndAdditionalQuantities_AppendsToSelectedCatalog() + { + UnitsNetSetup setup = UnitsNetSetup.Create(builder => builder.WithQuantities([Mass.Info], + selector => selector.WithAdditionalQuantities([HowMuch.Info]))); + + Assert.Equal([Mass.Info, HowMuch.Info], setup.Quantities.Infos); + } + + [Fact] + public void FactoryMethods_WithNullArguments_ThrowArgumentNullException() + { + var builder = new UnitsNetSetup.DefaultConfigurationBuilder(); + + Assert.Multiple(checks: + [ + () => Assert.Equal("configuration", + Assert.Throws(() => UnitsNetSetup.Create(null!)).ParamName), + () => Assert.Equal("quantities", + Assert.Throws(() => builder.WithQuantities((IEnumerable)null!)).ParamName), + () => Assert.Equal("quantities", + Assert.Throws(() => builder.WithQuantities((Func>)null!)).ParamName), + () => Assert.Equal("quantities", + Assert.Throws(() => builder.WithQuantities((IEnumerable)null!, _ => { })).ParamName), + () => Assert.Equal("configureQuantities", + Assert.Throws(() => builder.WithQuantities(() => [Mass.Info], null!)).ParamName) + ]); + } + [Fact] public void Builder_WithQuantitiesTwice_ThrowsInvalidOperationException() {