From 15fb7646e9840b392423eda11822ed819b8269cd Mon Sep 17 00:00:00 2001 From: Nice3point Date: Thu, 29 Oct 2026 14:26:01 +0300 Subject: [PATCH 1/3] [Element] Add a typed element lookup that returns null for an element of another type --- CHANGELOG.md | 21 ++++++ README.md | 7 ++ .../ElementIdExtensions.cs | 13 ++++ .../ElementIdExtensionsTests.cs | 65 +++++++++++++++++++ 4 files changed, 106 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 589fad8..fe8b623 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ - Fixed `GetRibbonPanels(Tab.AddIns)` and `CreatePanel` throwing `KeyNotFoundException` after `RemovePanel` removed the last panel of the "Add-Ins" or "Analyze" tab. +## Element lookup by type + +`ToElement` throws `InvalidCastException` when the id references an element of another type. +Treating such an id as no match used to drop out of the extensions: + +```c# +var view = document.GetElement(elementId) as View; +``` + +`ToElementOrDefault` returns `null` for an element of another type and for an id of no element, the way `FirstOrDefault` returns `null` for an empty sequence: + +```c# +var view = elementId.ToElementOrDefault(document); +if (view is null) +{ + return; +} +``` + +- `elementId.ToElementOrDefault(Document)` + # 2027.0.5 - Fixed `CreatePanel` adding a duplicate panel on every call for a built-in tab addressed by its title when the title differs from the tab ID. diff --git a/README.md b/README.md index 15ca7dc..ef5a920 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,13 @@ Element element = wallId.ToElement(document); Wall wall = wallId.ToElement(document); ``` +**ToElementOrDefault** retrieves the element of the specified type, or `null` when the ElementId references no element or an element of another type. +`ToElement` throws `InvalidCastException` for an element of another type. + +```csharp +View? view = elementId.ToElementOrDefault(document); +``` + **ToElements** retrieves a collection of elements associated with the specified ElementIds. ```csharp diff --git a/source/Nice3point.Revit.Extensions/ElementIdExtensions.cs b/source/Nice3point.Revit.Extensions/ElementIdExtensions.cs index d56bad8..05b5ff0 100644 --- a/source/Nice3point.Revit.Extensions/ElementIdExtensions.cs +++ b/source/Nice3point.Revit.Extensions/ElementIdExtensions.cs @@ -18,12 +18,25 @@ public static class ElementIdExtensions /// /// The expected type of the element. + /// The element is not of type . [Pure] public T? ToElement(Document document) where T : Element { return (T?)document.GetElement(elementId); } + /// + /// Gets the element of type referenced by the input ElementId. + /// + /// The expected type of the element. + /// The document that contains the element. + /// The element, or when the ElementId references no element or an element of another type. + [Pure] + public T? ToElementOrDefault(Document document) where T : Element + { + return document.GetElement(elementId) as T; + } + /// /// Retrieves the numeric value of the ElementId. /// diff --git a/tests/Nice3point.Revit.Extensions.Tests/ElementIdExtensionsTests.cs b/tests/Nice3point.Revit.Extensions.Tests/ElementIdExtensionsTests.cs index 572c32a..7bb36ff 100644 --- a/tests/Nice3point.Revit.Extensions.Tests/ElementIdExtensionsTests.cs +++ b/tests/Nice3point.Revit.Extensions.Tests/ElementIdExtensionsTests.cs @@ -180,6 +180,71 @@ public async Task ToElementGeneric_ValidElementId_ReturnsTypedElement(string pat } } + [Test] + [MethodDataSource(nameof(RevitFamilies))] + public async Task ToElementOrDefault_MatchingType_ReturnsTypedElement(string path) + { + // Arrange + var document = FamilyDocuments[path]; + var elementTypeId = new FilteredElementCollector(document) + .WhereElementIsElementType() + .FirstElementId(); + + if (elementTypeId == ElementId.InvalidElementId) + { + Skip.Test("No element types found in document"); + return; + } + + // Act + var elementType = elementTypeId.ToElementOrDefault(document); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(elementType).IsNotNull(); + await Assert.That(elementType!.Id).IsEqualTo(elementTypeId); + } + } + + [Test] + [MethodDataSource(nameof(RevitFamilies))] + public async Task ToElementOrDefault_DifferentType_ReturnsNull(string path) + { + // Arrange + var document = FamilyDocuments[path]; + var instanceId = new FilteredElementCollector(document) + .WhereElementIsNotElementType() + .FirstElementId(); + + if (instanceId == ElementId.InvalidElementId) + { + Skip.Test("No elements found in document"); + return; + } + + // Act + var elementType = instanceId.ToElementOrDefault(document); + + // Assert + await Assert.That(elementType).IsNull(); + } + + [Test] + [MethodDataSource(nameof(RevitFamilies))] + public async Task ToElementOrDefault_InvalidElementId_ReturnsNull(string path) + { + // Arrange + var document = FamilyDocuments[path]; + var invalidId = 999999999L.ToElementId(); + + // Act + var element = invalidId.ToElementOrDefault(document); + + // Assert + await Assert.That(element).IsNull(); + } + [Test] [MethodDataSource(nameof(RevitFamilies))] public async Task ToElements_MultipleElementIds_ReturnsAllElements(string path) From b820401dd83c803e8475f57068b3a5ef7044a40f Mon Sep 17 00:00:00 2001 From: Nice3point Date: Thu, 29 Oct 2026 14:27:52 +0300 Subject: [PATCH 2/3] [Parameter] Add a typed parameter element lookup that returns null for an element of another type --- CHANGELOG.md | 7 +- README.md | 7 ++ .../ParameterExtensions.cs | 12 +++ .../ParameterExtensionsTests.cs | 73 +++++++++++++++++++ 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 tests/Nice3point.Revit.Extensions.Tests/ParameterExtensionsTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index fe8b623..da3c51b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,14 @@ ## Element lookup by type -`ToElement` throws `InvalidCastException` when the id references an element of another type. +`ToElement` and `AsElement` throw `InvalidCastException` when the id references an element of another type. Treating such an id as no match used to drop out of the extensions: ```c# var view = document.GetElement(elementId) as View; ``` -`ToElementOrDefault` returns `null` for an element of another type and for an id of no element, the way `FirstOrDefault` returns `null` for an empty sequence: +`ToElementOrDefault` and `AsElementOrDefault` return `null` for an element of another type and for an id of no element, the way `FirstOrDefault` returns `null` for an empty sequence: ```c# var view = elementId.ToElementOrDefault(document); @@ -19,9 +19,12 @@ if (view is null) { return; } + +var material = parameter.AsElementOrDefault(); ``` - `elementId.ToElementOrDefault(Document)` +- `parameter.AsElementOrDefault()` # 2027.0.5 diff --git a/README.md b/README.md index ef5a920..01ed6af 100644 --- a/README.md +++ b/README.md @@ -745,6 +745,13 @@ Element value = element.FindParameter("Door material").AsElement(); Material value = element.FindParameter("Door material").AsElement(); ``` +**AsElementOrDefault** provides access to the Element of the specified type within the parameter, or `null` when the parameter references no element or an element of another type. +`AsElement` throws `InvalidCastException` for an element of another type. + +```csharp +Material? value = element.FindParameter("Door material").AsElementOrDefault(); +``` + **Set** sets the parameter to a new value. ```csharp diff --git a/source/Nice3point.Revit.Extensions/ParameterExtensions.cs b/source/Nice3point.Revit.Extensions/ParameterExtensions.cs index de3f8cd..8a2d0b5 100644 --- a/source/Nice3point.Revit.Extensions/ParameterExtensions.cs +++ b/source/Nice3point.Revit.Extensions/ParameterExtensions.cs @@ -56,6 +56,7 @@ public Color AsColor() /// The element contained in the parameter as an ElementId /// The AsElement method should only be used if the StorageType property returns that the internal contents of the parameter is an ElementId /// Type inherited from + /// The element is not of type [Pure] public T? AsElement() where T : Element { @@ -63,6 +64,17 @@ public Color AsColor() return elementId == ElementId.InvalidElementId ? null : (T)parameter.Element.Document.GetElement(elementId); } + /// Provides access to the Element of type within the parameter + /// The element contained in the parameter, or when the parameter references no element or an element of another type + /// The AsElementOrDefault method should only be used if the StorageType property returns that the internal contents of the parameter is an ElementId + /// Type inherited from + [Pure] + public T? AsElementOrDefault() where T : Element + { + var elementId = parameter.AsElementId(); + return elementId == ElementId.InvalidElementId ? null : parameter.Element.Document.GetElement(elementId) as T; + } + /// Sets the parameter to a new bool value /// The new bool value to which the parameter is to be set /// The Set method will return True if the parameter was successfully set to the new value, otherwise false diff --git a/tests/Nice3point.Revit.Extensions.Tests/ParameterExtensionsTests.cs b/tests/Nice3point.Revit.Extensions.Tests/ParameterExtensionsTests.cs new file mode 100644 index 0000000..f0d0e8f --- /dev/null +++ b/tests/Nice3point.Revit.Extensions.Tests/ParameterExtensionsTests.cs @@ -0,0 +1,73 @@ +using Nice3point.Revit.Extensions.Tests.Abstractions; + +namespace Nice3point.Revit.Extensions.Tests; + +public sealed class ParameterExtensionsTests : RevitFamilySampleTest +{ + [Test] + [MethodDataSource(nameof(RevitFamilies))] + public async Task AsElementOrDefault_MatchingType_ReturnsTypedElement(string path) + { + // Arrange + var document = FamilyDocuments[path]; + var parameter = FindInstanceParameter(document); + if (parameter is null) + { + Skip.Test("No parameter references an element instance in document"); + return; + } + + // Act + var element = parameter.AsElementOrDefault(); + + // Assert + using (Assert.Multiple()) + { + await Assert.That(element).IsNotNull(); + await Assert.That(element!.Id).IsEqualTo(parameter.AsElementId()); + } + } + + [Test] + [MethodDataSource(nameof(RevitFamilies))] + public async Task AsElementOrDefault_DifferentType_ReturnsNull(string path) + { + // Arrange + var document = FamilyDocuments[path]; + var parameter = FindInstanceParameter(document); + if (parameter is null) + { + Skip.Test("No parameter references an element instance in document"); + return; + } + + // Act + var elementType = parameter.AsElementOrDefault(); + + // Assert + await Assert.That(elementType).IsNull(); + } + + private static Parameter? FindInstanceParameter(Document document) + { + var elements = new FilteredElementCollector(document).WhereElementIsNotElementType(); + foreach (var element in elements) + { + foreach (Parameter parameter in element.Parameters) + { + if (parameter.StorageType != StorageType.ElementId) + { + continue; + } + + var referencedElement = document.GetElement(parameter.AsElementId()); + if (referencedElement is not null and not ElementType) + { + return parameter; + } + } + } + + return null; + } +} From a46a6b82519b043fd9452f62d620b53949db409e Mon Sep 17 00:00:00 2001 From: Nice3point Date: Thu, 29 Oct 2026 14:40:54 +0300 Subject: [PATCH 3/3] [Element][Parameter] Throw from the element lookups for a missing element --- CHANGELOG.md | 38 +++++++++-- README.md | 12 ++-- .../ElementIdExtensions.cs | 29 ++++++-- .../ParameterExtensions.cs | 23 +++++-- .../ElementIdExtensionsTests.cs | 59 +++++++++++++--- .../ParameterExtensionsTests.cs | 67 +++++++++++++++++-- 6 files changed, 186 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da3c51b..702c2a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,16 +2,25 @@ - Fixed `GetRibbonPanels(Tab.AddIns)` and `CreatePanel` throwing `KeyNotFoundException` after `RemovePanel` removed the last panel of the "Add-Ins" or "Analyze" tab. -## Element lookup by type +## Element lookup -`ToElement` and `AsElement` throw `InvalidCastException` when the id references an element of another type. -Treating such an id as no match used to drop out of the extensions: +Element lookups follow the convention of LINQ `First` and `FirstOrDefault`. +`ToElement` and `AsElement` return the element or throw, and `ToElementOrDefault` and `AsElementOrDefault` return the element or `null`: + +| Member | No element | Element of another type | +|-----------------------------------------------|-----------------------------|-------------------------| +| `ToElement`, `ToElement` | `InvalidOperationException` | `InvalidCastException` | +| `AsElement`, `AsElement` | `InvalidOperationException` | `InvalidCastException` | +| `ToElementOrDefault`, `ToElementOrDefault` | `null` | `null` | +| `AsElementOrDefault`, `AsElementOrDefault` | `null` | `null` | + +Resolving an id that may reference another element used to drop out of the extensions: ```c# var view = document.GetElement(elementId) as View; ``` -`ToElementOrDefault` and `AsElementOrDefault` return `null` for an element of another type and for an id of no element, the way `FirstOrDefault` returns `null` for an empty sequence: +Now it is one call: ```c# var view = elementId.ToElementOrDefault(document); @@ -19,13 +28,30 @@ if (view is null) { return; } - -var material = parameter.AsElementOrDefault(); ``` +- `elementId.ToElementOrDefault(Document)` - `elementId.ToElementOrDefault(Document)` +- `parameter.AsElementOrDefault()` - `parameter.AsElementOrDefault()` +## Breaking changes + +**`ToElement` and `AsElement` throw for a missing element.** +`ToElement`, `ToElement`, `AsElement`, and `AsElement` returned `null` when the id or the parameter referenced no element. +They throw `InvalidOperationException` for it, and their return types are not nullable. +Code that checks the result for `null` switches to the `OrDefault` counterpart: + +```c# +// Before +var level = parameter.AsElement(); +if (level is null) return; + +// After +var level = parameter.AsElementOrDefault(); +if (level is null) return; +``` + # 2027.0.5 - Fixed `CreatePanel` adding a duplicate panel on every call for a built-in tab addressed by its title when the title differs from the tab ID. diff --git a/README.md b/README.md index 01ed6af..2e68d0b 100644 --- a/README.md +++ b/README.md @@ -112,17 +112,17 @@ Package included by default in [Revit Templates](https://github.com/Nice3point/R ## Element -**ToElement** retrieves the element associated with the specified ElementId. +**ToElement** retrieves the element associated with the specified ElementId, and throws when the ElementId references no element or an element of another type. ```csharp Element element = wallId.ToElement(document); Wall wall = wallId.ToElement(document); ``` -**ToElementOrDefault** retrieves the element of the specified type, or `null` when the ElementId references no element or an element of another type. -`ToElement` throws `InvalidCastException` for an element of another type. +**ToElementOrDefault** retrieves the element, or `null` when the ElementId references no element or an element of another type. ```csharp +Element? element = elementId.ToElementOrDefault(document); View? view = elementId.ToElementOrDefault(document); ``` @@ -738,17 +738,17 @@ bool value = element.FindParameter("IsClosed").AsBool(); Color value = element.FindParameter("Door color").AsColor(); ``` -**AsElement** provides access to the Element within the parameter. +**AsElement** provides access to the Element within the parameter, and throws when the parameter references no element or an element of another type. ```csharp Element value = element.FindParameter("Door material").AsElement(); Material value = element.FindParameter("Door material").AsElement(); ``` -**AsElementOrDefault** provides access to the Element of the specified type within the parameter, or `null` when the parameter references no element or an element of another type. -`AsElement` throws `InvalidCastException` for an element of another type. +**AsElementOrDefault** provides access to the Element within the parameter, or `null` when the parameter references no element or an element of another type. ```csharp +Element? value = element.FindParameter("Door material").AsElementOrDefault(); Material? value = element.FindParameter("Door material").AsElementOrDefault(); ``` diff --git a/source/Nice3point.Revit.Extensions/ElementIdExtensions.cs b/source/Nice3point.Revit.Extensions/ElementIdExtensions.cs index 05b5ff0..d0cf46b 100644 --- a/source/Nice3point.Revit.Extensions/ElementIdExtensions.cs +++ b/source/Nice3point.Revit.Extensions/ElementIdExtensions.cs @@ -9,20 +9,37 @@ public static class ElementIdExtensions /// The unique identification for an element. extension(ElementId elementId) { - /// + /// + /// Gets the element referenced by the input ElementId. + /// + /// The document that contains the element. + /// The element. + /// The ElementId references no element in the document. [Pure] - public Element? ToElement(Document document) + public Element ToElement(Document document) { - return document.GetElement(elementId); + return document.GetElement(elementId) ?? throw new InvalidOperationException($"The document contains no element with the id {elementId.ToLong()}."); } - /// + /// + /// Gets the element of type referenced by the input ElementId. + /// /// The expected type of the element. + /// The document that contains the element. + /// The element. + /// The ElementId references no element in the document. /// The element is not of type . [Pure] - public T? ToElement(Document document) where T : Element + public T ToElement(Document document) where T : Element + { + return (T)elementId.ToElement(document); + } + + /// + [Pure] + public Element? ToElementOrDefault(Document document) { - return (T?)document.GetElement(elementId); + return document.GetElement(elementId); } /// diff --git a/source/Nice3point.Revit.Extensions/ParameterExtensions.cs b/source/Nice3point.Revit.Extensions/ParameterExtensions.cs index 8a2d0b5..ef257c7 100644 --- a/source/Nice3point.Revit.Extensions/ParameterExtensions.cs +++ b/source/Nice3point.Revit.Extensions/ParameterExtensions.cs @@ -45,23 +45,33 @@ public Color AsColor() /// Provides access to the Element within the parameter /// The element contained in the parameter as an ElementId /// The AsElement method should only be used if the StorageType property returns that the internal contents of the parameter is an ElementId + /// The parameter references no element [Pure] - public Element? AsElement() + public Element AsElement() { - var elementId = parameter.AsElementId(); - return elementId == ElementId.InvalidElementId ? null : parameter.Element.Document.GetElement(elementId); + return parameter.AsElementOrDefault() ?? throw new InvalidOperationException($"The parameter '{parameter.Definition.Name}' references no element."); } /// Provides access to the Element within the parameter /// The element contained in the parameter as an ElementId /// The AsElement method should only be used if the StorageType property returns that the internal contents of the parameter is an ElementId /// Type inherited from + /// The parameter references no element /// The element is not of type [Pure] - public T? AsElement() where T : Element + public T AsElement() where T : Element + { + return (T)parameter.AsElement(); + } + + /// Provides access to the Element within the parameter + /// The element contained in the parameter, or when the parameter references no element + /// The AsElementOrDefault method should only be used if the StorageType property returns that the internal contents of the parameter is an ElementId + [Pure] + public Element? AsElementOrDefault() { var elementId = parameter.AsElementId(); - return elementId == ElementId.InvalidElementId ? null : (T)parameter.Element.Document.GetElement(elementId); + return elementId == ElementId.InvalidElementId ? null : parameter.Element.Document.GetElement(elementId); } /// Provides access to the Element of type within the parameter @@ -71,8 +81,7 @@ public Color AsColor() [Pure] public T? AsElementOrDefault() where T : Element { - var elementId = parameter.AsElementId(); - return elementId == ElementId.InvalidElementId ? null : parameter.Element.Document.GetElement(elementId) as T; + return parameter.AsElementOrDefault() as T; } /// Sets the parameter to a new bool value diff --git a/tests/Nice3point.Revit.Extensions.Tests/ElementIdExtensionsTests.cs b/tests/Nice3point.Revit.Extensions.Tests/ElementIdExtensionsTests.cs index 7bb36ff..c115078 100644 --- a/tests/Nice3point.Revit.Extensions.Tests/ElementIdExtensionsTests.cs +++ b/tests/Nice3point.Revit.Extensions.Tests/ElementIdExtensionsTests.cs @@ -134,19 +134,58 @@ public async Task ToElement_ValidElementId_ReturnsElement(string path) [Test] [MethodDataSource(nameof(RevitFamilies))] - public async Task ToElement_InvalidElementId_ReturnsNull(string path) + public async Task ToElement_InvalidElementId_ThrowsInvalidOperationException(string path) { // Arrange var document = FamilyDocuments[path]; + var invalidId = 999999999L.ToElementId(); + + // Act & Assert + await Assert.That(() => invalidId.ToElement(document)).Throws(); + } -#if REVIT2024_OR_GREATER - var invalidId = new ElementId(999999999L); -#else - var invalidId = new ElementId(999999999); -#endif + [Test] + [MethodDataSource(nameof(RevitFamilies))] + public async Task ToElementGeneric_InvalidElementId_ThrowsInvalidOperationException(string path) + { + // Arrange + var document = FamilyDocuments[path]; + var invalidId = 999999999L.ToElementId(); + + // Act & Assert + await Assert.That(() => invalidId.ToElement(document)).Throws(); + } + + [Test] + [MethodDataSource(nameof(RevitFamilies))] + public async Task ToElementGeneric_DifferentType_ThrowsInvalidCastException(string path) + { + // Arrange + var document = FamilyDocuments[path]; + var instanceId = new FilteredElementCollector(document) + .WhereElementIsNotElementType() + .FirstElementId(); + + if (instanceId == ElementId.InvalidElementId) + { + Skip.Test("No elements found in document"); + return; + } + + // Act & Assert + await Assert.That(() => instanceId.ToElement(document)).Throws(); + } + + [Test] + [MethodDataSource(nameof(RevitFamilies))] + public async Task ToElementOrDefault_InvalidElementId_ReturnsNull(string path) + { + // Arrange + var document = FamilyDocuments[path]; + var invalidId = 999999999L.ToElementId(); // Act - var element = invalidId.ToElement(document); + var element = invalidId.ToElementOrDefault(document); // Assert await Assert.That(element).IsNull(); @@ -182,7 +221,7 @@ public async Task ToElementGeneric_ValidElementId_ReturnsTypedElement(string pat [Test] [MethodDataSource(nameof(RevitFamilies))] - public async Task ToElementOrDefault_MatchingType_ReturnsTypedElement(string path) + public async Task ToElementOrDefaultGeneric_MatchingType_ReturnsTypedElement(string path) { // Arrange var document = FamilyDocuments[path]; @@ -209,7 +248,7 @@ public async Task ToElementOrDefault_MatchingType_ReturnsTypedElement(string pat [Test] [MethodDataSource(nameof(RevitFamilies))] - public async Task ToElementOrDefault_DifferentType_ReturnsNull(string path) + public async Task ToElementOrDefaultGeneric_DifferentType_ReturnsNull(string path) { // Arrange var document = FamilyDocuments[path]; @@ -232,7 +271,7 @@ public async Task ToElementOrDefault_DifferentType_ReturnsNull(string path) [Test] [MethodDataSource(nameof(RevitFamilies))] - public async Task ToElementOrDefault_InvalidElementId_ReturnsNull(string path) + public async Task ToElementOrDefaultGeneric_InvalidElementId_ReturnsNull(string path) { // Arrange var document = FamilyDocuments[path]; diff --git a/tests/Nice3point.Revit.Extensions.Tests/ParameterExtensionsTests.cs b/tests/Nice3point.Revit.Extensions.Tests/ParameterExtensionsTests.cs index f0d0e8f..622d636 100644 --- a/tests/Nice3point.Revit.Extensions.Tests/ParameterExtensionsTests.cs +++ b/tests/Nice3point.Revit.Extensions.Tests/ParameterExtensionsTests.cs @@ -6,11 +6,65 @@ public sealed class ParameterExtensionsTests : RevitFamilySampleTest { [Test] [MethodDataSource(nameof(RevitFamilies))] - public async Task AsElementOrDefault_MatchingType_ReturnsTypedElement(string path) + public async Task AsElement_NoElement_ThrowsInvalidOperationException(string path) { // Arrange var document = FamilyDocuments[path]; - var parameter = FindInstanceParameter(document); + var parameter = FindElementIdParameter(document, static element => element is null); + if (parameter is null) + { + Skip.Test("No parameter references no element in document"); + return; + } + + // Act & Assert + await Assert.That(() => parameter.AsElement()).Throws(); + } + + [Test] + [MethodDataSource(nameof(RevitFamilies))] + public async Task AsElementGeneric_DifferentType_ThrowsInvalidCastException(string path) + { + // Arrange + var document = FamilyDocuments[path]; + var parameter = FindElementIdParameter(document, static element => element is not null and not ElementType); + if (parameter is null) + { + Skip.Test("No parameter references an element instance in document"); + return; + } + + // Act & Assert + await Assert.That(() => parameter.AsElement()).Throws(); + } + + [Test] + [MethodDataSource(nameof(RevitFamilies))] + public async Task AsElementOrDefault_NoElement_ReturnsNull(string path) + { + // Arrange + var document = FamilyDocuments[path]; + var parameter = FindElementIdParameter(document, static element => element is null); + if (parameter is null) + { + Skip.Test("No parameter references no element in document"); + return; + } + + // Act + var element = parameter.AsElementOrDefault(); + + // Assert + await Assert.That(element).IsNull(); + } + + [Test] + [MethodDataSource(nameof(RevitFamilies))] + public async Task AsElementOrDefaultGeneric_MatchingType_ReturnsTypedElement(string path) + { + // Arrange + var document = FamilyDocuments[path]; + var parameter = FindElementIdParameter(document, static element => element is not null and not ElementType); if (parameter is null) { Skip.Test("No parameter references an element instance in document"); @@ -30,11 +84,11 @@ public async Task AsElementOrDefault_MatchingType_ReturnsTypedElement(string pat [Test] [MethodDataSource(nameof(RevitFamilies))] - public async Task AsElementOrDefault_DifferentType_ReturnsNull(string path) + public async Task AsElementOrDefaultGeneric_DifferentType_ReturnsNull(string path) { // Arrange var document = FamilyDocuments[path]; - var parameter = FindInstanceParameter(document); + var parameter = FindElementIdParameter(document, static element => element is not null and not ElementType); if (parameter is null) { Skip.Test("No parameter references an element instance in document"); @@ -48,7 +102,7 @@ public async Task AsElementOrDefault_DifferentType_ReturnsNull(string path) await Assert.That(elementType).IsNull(); } - private static Parameter? FindInstanceParameter(Document document) + private static Parameter? FindElementIdParameter(Document document, Func referencedElementPredicate) { var elements = new FilteredElementCollector(document).WhereElementIsNotElementType(); foreach (var element in elements) @@ -60,8 +114,7 @@ public async Task AsElementOrDefault_DifferentType_ReturnsNull(string path) continue; } - var referencedElement = document.GetElement(parameter.AsElementId()); - if (referencedElement is not null and not ElementType) + if (referencedElementPredicate(document.GetElement(parameter.AsElementId()))) { return parameter; }