diff --git a/src/.idea/.idea.L5Sharp/.idea/workspace.xml b/src/.idea/.idea.L5Sharp/.idea/workspace.xml
index 7682f31d..a8a460c9 100644
--- a/src/.idea/.idea.L5Sharp/.idea/workspace.xml
+++ b/src/.idea/.idea.L5Sharp/.idea/workspace.xml
@@ -8,9 +8,48 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -80,38 +119,38 @@
- {
+ "keyToString": {
+ "Notification.DisplayName-DoNotAsk-Plugin Error": "Plugins failed to load",
+ "Notification.DoNotAsk-Plugin Error": "true",
+ "RunOnceActivity.OpenProjectViewOnStart": "true",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "TODO_SCOPE": "All Places",
+ "WebServerToolWindowFactoryState": "false",
+ "a7604713-2340-41ef-8914-9a0e821f9675.executor": "Debug",
+ "bb4529a5-d289-453a-84c6-89ce8148c18f.executor": "Debug",
+ "git-widget-placeholder": "main",
+ "ignore.virus.scanning.warn.message": "true",
+ "last_opened_file_path": "C:/Users/tnunnink/Documents/GitHub/L5Sharp/src/L5Sharp.sln",
+ "node.js.detected.package.eslint": "true",
+ "node.js.detected.package.tslint": "true",
+ "node.js.selected.package.eslint": "(autodetect)",
+ "node.js.selected.package.tslint": "(autodetect)",
+ "nodejs_package_manager_path": "npm",
+ "settings.editor.selected.configurable": "org.jetbrains.plugins.github.ui.GithubSettingsConfigurable",
+ "vue.rearranger.settings.migration": "true"
},
- "keyToStringList": {
- "com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
- "C#"
+ "keyToStringList": {
+ "com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
+ "C#"
],
- "rider.external.source.directories": [
- "C:\\Users\\tnunnink\\AppData\\Roaming\\JetBrains\\Rider2023.2\\resharper-host\\DecompilerCache",
- "C:\\Users\\tnunnink\\AppData\\Roaming\\JetBrains\\Rider2023.2\\resharper-host\\SourcesCache",
- "C:\\Users\\tnunnink\\AppData\\Local\\Symbols\\src"
+ "rider.external.source.directories": [
+ "C:\\Users\\tnunnink\\AppData\\Roaming\\JetBrains\\Rider2023.2\\resharper-host\\DecompilerCache",
+ "C:\\Users\\tnunnink\\AppData\\Roaming\\JetBrains\\Rider2023.2\\resharper-host\\SourcesCache",
+ "C:\\Users\\tnunnink\\AppData\\Local\\Symbols\\src"
]
}
-}]]>
+}
C:\Users\tnunnink\AppData\Roaming\Subversion
@@ -363,7 +402,8 @@
-
+
+ 1677621948966
@@ -708,7 +748,7 @@
1687451515386
-
+
@@ -718,7 +758,7 @@
-
+
@@ -744,7 +784,6 @@
-
@@ -769,7 +808,8 @@
-
+
+
diff --git a/src/L5Sharp/Components/Task.cs b/src/L5Sharp/Components/Task.cs
index 834e1420..31020182 100644
--- a/src/L5Sharp/Components/Task.cs
+++ b/src/L5Sharp/Components/Task.cs
@@ -36,6 +36,8 @@ public Task()
Priority = new TaskPriority(10);
Rate = new ScanRate(10);
Watchdog = new Watchdog(500);
+ InhibitTask = false;
+ DisableUpdateOutputs = false;
}
///
@@ -51,7 +53,7 @@ public Task(XElement element) : base(element)
/// Gets the type of the task component (Continuous, Periodic, Event).
///
/// A enum representing the type of the task.
- public TaskType? Type
+ public TaskType Type
{
get => GetRequiredValue();
set => SetRequiredValue(value);
@@ -174,7 +176,7 @@ public void Schedule(string program)
}
///
- /// Removes the specified program name from the underlying list of
+ /// Removes the specified program name from the underlying list of programs.
///
/// The name of the program to cancel.
public void Cancel(string program)
diff --git a/src/L5Sharp/Elements/DiagramElement.cs b/src/L5Sharp/Elements/DiagramElement.cs
index 879e5294..ab75610b 100644
--- a/src/L5Sharp/Elements/DiagramElement.cs
+++ b/src/L5Sharp/Elements/DiagramElement.cs
@@ -149,4 +149,29 @@ public override bool Equals(object? obj)
///
public override int GetHashCode() => ID.GetHashCode();
+
+ ///
+ /// Gets a collection of values for the specified attribute name parsed as the specified generic type parameter if it exists.
+ /// If the element does not exist, returns an empty collection of the generic type parameter.
+ ///
+ /// The name of the attribute.
+ /// The value separator character. Default is ' '.
+ /// The return type of the value.
+ ///
+ /// If found, all values of the attribute split on the specified separator and parsed as the generic type parameter.
+ /// If not found, returns an empty collection.
+ ///
+ ///
+ /// This method makes getting/setting attributes with collection of values as a single string with a certain separator
+ /// character as concise as possible for derived classes. This method is added here since only types like
+ /// are using this method overload.
+ ///
+ protected IEnumerable GetValues(string name, char separator = ' ')
+ {
+ var value = Element.Attribute(name)?.Value;
+
+ return value is not null
+ ? value.Split(separator, StringSplitOptions.RemoveEmptyEntries).Select(v => v.Parse())
+ : Enumerable.Empty();
+ }
}
\ No newline at end of file
diff --git a/src/L5Sharp/L5Sharp.csproj b/src/L5Sharp/L5Sharp.csproj
index 2d4e7597..feaf6dd5 100644
--- a/src/L5Sharp/L5Sharp.csproj
+++ b/src/L5Sharp/L5Sharp.csproj
@@ -8,9 +8,9 @@
trueL5SharpTimothy Nunnink
- 0.16.0
- 0.16.0
- 0.16.0.0
+ 0.16.1
+ 0.16.1
+ 0.16.1.0A library for intuitively interacting with Rockwell's L5X import/export files.https://github.com/tnunnink/L5Sharpcsharp allen-bradely l5x logix plc-programming rockwell-automation logix5000
diff --git a/src/L5Sharp/LogixElement.cs b/src/L5Sharp/LogixElement.cs
index 17c42568..5d0db335 100644
--- a/src/L5Sharp/LogixElement.cs
+++ b/src/L5Sharp/LogixElement.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -14,7 +13,8 @@ namespace L5Sharp;
/// A base class for all types that can be serialized and deserialized from a L5X file. This abstract class enforces
/// the interface and a constructor taking a for initialization
/// of and underlying element object. Deriving classes will have access to the underlying element and
-/// methods for easily getting and setting data.
+/// methods for easily getting and setting data. Implementing classes need to also provide at least a constructor taking
+/// a single and pass it to the base constructor to be deserializable by the library.
///
[PublicAPI]
public abstract class LogixElement : ILogixSerializable
@@ -75,23 +75,23 @@ protected LogixElement(XElement element)
///
/// A representing the name of the L5X element type.
///
- /// The "L5XType" is nothing more than the name of the underlying element for the object.
- /// Most L5X elements correspond to a class type of the library with the same or similar name.
- /// Each class type in this library can also support multiple element types (e.g. Tag/LocalTag/ConfigTag).
- /// This property will indicate the actual type of the underlying element as opposed to the actual type you
- /// can retrieve from GetType().
+ /// The "L5XType" is nothing more than the name of the underlying for the object.
+ /// Most L5X element names correspond to a class type of the library with the same or similar name.
+ /// However, each class type in this library can also support multiple element types (e.g. Tag/LocalTag/ConfigTag).
+ /// This property will indicate the actual type of the underlying element as opposed to the actual class type of
+ /// the object.
///
public string L5XType => Element.Name.LocalName;
///
- /// The scope of the LogixElement, indicating whether it is a globally scoped controller element,
+ /// The scope of the element, indicating whether it is a globally scoped controller element,
/// a locally scoped program or instruction element, or neither (not attached to L5X tree).
///
/// A option indicating the scope type for the element.
///
///
/// The scope of an element is determined from the ancestors of the underlying .
- /// If the ancestor is program element first, it is Program scoped.
+ /// If the ancestor is a program element first, it is Program scoped.
/// If the ancestor is a AddOnInstructionDefinition element first, it is Instruction scoped.
/// If the ancestor is a controller element first, it is Controller scoped.
/// If no ancestor is found, we assume the component has Null scope, meaning it is not attached to a L5X tree.
@@ -105,8 +105,8 @@ protected LogixElement(XElement element)
public Scope Scope => Scope.Type(Element);
///
- /// The name of the ancestral LogixElement, indicating the program, instruction, or controller for which
- /// it is contained.
+ /// The logix name of an ancestral element, indicating the program, instruction, or controller
+ /// for which this element is contained. This is essentially the scope name for a given logix element.
///
///
/// A representing the name of the program, instruction, or controller in which this component
@@ -213,7 +213,7 @@ public void AddBefore(LogixElement element)
/// Converts this element to the specified element type name.
///
/// The name of the element type to convert this logix element to.
- ///
+ /// A new instance with the converted element name.
/// typeName is null or empty.
/// The specified type name is not supported by this class type.
///
@@ -229,7 +229,7 @@ public LogixElement Convert(string typeName)
throw new ArgumentException("Type name can not be null or empty to perform conversion", nameof(typeName));
if (!GetType().L5XTypes().Contains(typeName))
- throw new InvalidOperationException($"Can not convert {L5XType} to type {typeName}");
+ throw new InvalidOperationException($"Can not convert element type '{L5XType}' to type '{typeName}'.");
if (L5XType == typeName) return this;
@@ -238,11 +238,13 @@ public LogixElement Convert(string typeName)
}
///
- /// Converts this element to the specified element type name.
+ /// Converts this element to the specified element type name.
///
- /// The name of the element type to convert this logix element to.
- ///
- /// typeName is null or empty.
+ /// The name of the element type to convert this logix element to. This will default to the
+ /// first configured L5XType of the specified logix type parameter, but can optionally be provided if the type
+ /// to convert to is not the default L5XType name.
+ /// The type to convert this element type to.
+ /// A new of the specified generic type with the converted element name.
/// The specified type name is not supported by this class type.
///
/// This simply updates the underlying element name to the provided name if it is a supported type name for this
@@ -256,7 +258,7 @@ public TElement Convert(string? typeName = null) where TElement : Logi
typeName ??= typeof(TElement).L5XType();
if (!GetType().L5XTypes().Contains(typeName))
- throw new InvalidOperationException($"Can not convert {L5XType} to type {typeName}");
+ throw new InvalidOperationException($"Can not convert element type '{L5XType}' to type '{typeName}'.");
if (L5XType == typeName) return (TElement)this;
@@ -521,31 +523,6 @@ protected LogixContainer GetContainer([CallerMemberName] string?
: default;
}
- ///
- /// Gets the value of the specified child element parsed as the specified generic type parameter if it exists.
- /// If the element does not exist, returns default value of the generic type parameter.
- ///
- /// The name of the child element.
- ///
- /// The return type of the value.
- ///
- /// If found, the value of child element parsed as the generic type parameter.
- /// If not found, returns default.
- ///
- ///
- /// This method makes getting/setting data on as concise as possible from derived classes.
- /// This method uses the so the deriving classes don't have to specify
- /// the property name (assuming its the name matches the underlying element property).
- ///
- protected IEnumerable GetValues(string name, char separator = ' ')
- {
- var value = Element.Attribute(name)?.Value;
-
- return value is not null
- ? value.Split(separator, StringSplitOptions.RemoveEmptyEntries).Select(v => v.Parse())
- : Enumerable.Empty();
- }
-
///
/// Sets the value of an attribute, adds an attribute, or removes an attribute.
///
@@ -656,13 +633,14 @@ protected void SetRequiredValue(T value, [CallerMemberName] string? name = nu
///
protected void SetProperty(T value, [CallerMemberName] string? name = null)
{
+ var element = Element.Element(name);
+
if (value is null)
{
- Element.Element(name)?.Remove();
+ element?.Remove();
return;
}
-
- var element = Element.Element(name);
+
if (element is null)
{
Element.Add(new XElement(name, new XCData(value.ToString())));
@@ -719,20 +697,21 @@ protected void SetComplex(T? value, [CallerMemberName] string? name = null) w
protected void SetContainer(LogixContainer? value, [CallerMemberName] string? name = null)
where TChild : LogixElement
{
+ var element = Element.Element(name);
+
if (value is null)
{
- Element.Element(name)?.Remove();
+ element?.Remove();
return;
}
-
- var existing = Element.Element(name);
- if (existing is null)
+
+ if (element is null)
{
Element.Add(value.Serialize());
return;
}
- existing.ReplaceWith(value.Serialize());
+ element.ReplaceWith(value.Serialize());
}
///
@@ -761,7 +740,7 @@ protected void SetDateTime(DateTime? value, string? format = null, [CallerMember
///
/// Adds, removes, or updates the common logix description child element on the current underlying element object.
- /// If null, will remove the child element. If not null, will either add as the first chile element or replace the
+ /// If null, will remove the child element. If not null, will either add as the first child element or replace the
/// existing child element.
///
/// The description value to set.
diff --git a/src/L5Sharp/LogixSerializer.cs b/src/L5Sharp/LogixSerializer.cs
index b6adf488..9d37a3de 100644
--- a/src/L5Sharp/LogixSerializer.cs
+++ b/src/L5Sharp/LogixSerializer.cs
@@ -10,6 +10,11 @@ namespace L5Sharp;
///
/// A static deserialization class for objects and their derivatives.
+/// This class uses a dictionary to cache deserialization functions for types deriving from LogixElement.
+/// We are using compiled expression functions as they are more performant that invoking constructors via reflection.
+/// We are also caching them for reuse so we don't have to build them each time we call .
+/// Users can register custom implementations of a LogixElement using , which will
+/// allow the type to be deserialized as calls to the type are made throughout the library.
///
public static class LogixSerializer
{
@@ -60,7 +65,44 @@ public static LogixElement Deserialize(this XElement element)
$"Could not find deserializable type for element {element.Name}.");
}
}
-
+
+ ///
+ /// Registers the specified logix element type to the global logix serialization cache to be used for
+ /// deserializing the type.
+ ///
+ /// The type to register.
+ /// The specified type is not deserializable, meaning it does not inherit
+ /// from , is not public, is abstract, or does not have a constructor taking a single
+ /// parameter.
+ /// A type with the same name (L5XType) is already registered.
+ ///
+ /// Not that this is common, but this gives external users a means to register custom
+ /// derived types with the library so that they can be deserialized and used thought other parts of the library.
+ ///
+ public static void Register() where TElement : LogixElement
+ {
+ var type = typeof(TElement);
+
+ if (!IsDeserializableType(type))
+ {
+ var explanation =
+ $"The type must derive from {typeof(LogixElement)}, be public, non-abstract," +
+ $" and have a constructor accepting a single {typeof(XElement)}";
+ throw new ArgumentException(
+ $"Type '{typeof(TElement)} is not a valid deserializable logix type. {explanation}");
+ }
+
+ var deserializer = type.Deserializer();
+ var deserializers = type.L5XTypes()
+ .Select(t => new KeyValuePair>(t, deserializer));
+
+ foreach (var pair in deserializers)
+ {
+ if (!Deserializers.Value.TryAdd(pair.Key, pair.Value))
+ throw new InvalidOperationException($"Type '{pair.Key}' is already registered with another function.");
+ }
+ }
+
///
/// Performs reflection scanning of provided to get all public non abstract types
/// inheriting from that have the supported deserialization constructor,
diff --git a/tests/L5Sharp.Tests/L5Sharp.Tests.csproj b/tests/L5Sharp.Tests/L5Sharp.Tests.csproj
index 0dc11d7b..e8391223 100644
--- a/tests/L5Sharp.Tests/L5Sharp.Tests.csproj
+++ b/tests/L5Sharp.Tests/L5Sharp.Tests.csproj
@@ -5,6 +5,7 @@
true10net7.0
+ enable
@@ -255,6 +256,36 @@
SheetTests.cs
+
+ LogixElementTests.cs
+
+
+ LogixElementTests.cs
+
+
+ LogixElementTests.cs
+
+
+ LogixElementTests.cs
+
+
+ LogixElementTests.cs
+
+
+ LogixElementTests.cs
+
+
+ LogixElementTests.cs
+
+
+ LogixElementTests.cs
+
+
+ LogixElementTests.cs
+
+
+ LogixElementTests.cs
+
diff --git a/tests/L5Sharp.Tests/LogixElementTests.AddAfter_AlternateType_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.AddAfter_AlternateType_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..d5018ac1
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.AddAfter_AlternateType_ShouldBeVerified.verified.txt
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.AddAfter_ValidElement_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.AddAfter_ValidElement_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..d5018ac1
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.AddAfter_ValidElement_ShouldBeVerified.verified.txt
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.AddBefore_AlternateType_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.AddBefore_AlternateType_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..405e4adf
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.AddBefore_AlternateType_ShouldBeVerified.verified.txt
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.AddBefore_ValidElement_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.AddBefore_ValidElement_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..405e4adf
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.AddBefore_ValidElement_ShouldBeVerified.verified.txt
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.Remove_HasParent_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.Remove_HasParent_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..a9303e11
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.Remove_HasParent_ShouldBeVerified.verified.txt
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.Replace_AlternateType_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.Replace_AlternateType_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..57528b06
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.Replace_AlternateType_ShouldBeVerified.verified.txt
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.Replace_ValidElement_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.Replace_ValidElement_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..57528b06
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.Replace_ValidElement_ShouldBeVerified.verified.txt
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetChildValue_HasValueDifferentValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetChildValue_HasValueDifferentValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..2333b9ae
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetChildValue_HasValueDifferentValue_ShouldBeVerified.verified.txt
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetChildValue_HasValueToNull_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetChildValue_HasValueToNull_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..e146580b
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetChildValue_HasValueToNull_ShouldBeVerified.verified.txt
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetChildValue_NoValueToValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetChildValue_NoValueToValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..d3e1333b
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetChildValue_NoValueToValue_ShouldBeVerified.verified.txt
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetComplex_HasValueDifferentValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetComplex_HasValueDifferentValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..73c688ee
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetComplex_HasValueDifferentValue_ShouldBeVerified.verified.txt
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetComplex_HasValueToNull_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetComplex_HasValueToNull_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..f140f3fe
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetComplex_HasValueToNull_ShouldBeVerified.verified.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetComplex_NoValueToValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetComplex_NoValueToValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..d6d8dc20
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetComplex_NoValueToValue_ShouldBeVerified.verified.txt
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetContainer_HasValueDifferentValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetContainer_HasValueDifferentValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..6dfcde7a
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetContainer_HasValueDifferentValue_ShouldBeVerified.verified.txt
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetContainer_HasValueToNull_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetContainer_HasValueToNull_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..f140f3fe
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetContainer_HasValueToNull_ShouldBeVerified.verified.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetContainer_NoValueCollectionWithElements_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetContainer_NoValueCollectionWithElements_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..16849149
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetContainer_NoValueCollectionWithElements_ShouldBeVerified.verified.txt
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetContainer_NoValueToEmptyCollection_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetContainer_NoValueToEmptyCollection_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..25545cbd
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetContainer_NoValueToEmptyCollection_ShouldBeVerified.verified.txt
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetDateTime_HasValueDifferentValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetDateTime_HasValueDifferentValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..e07cc080
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetDateTime_HasValueDifferentValue_ShouldBeVerified.verified.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetDateTime_HasValueToNull_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetDateTime_HasValueToNull_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..f140f3fe
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetDateTime_HasValueToNull_ShouldBeVerified.verified.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetDateTime_NoValueToValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetDateTime_NoValueToValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..9d86be60
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetDateTime_NoValueToValue_ShouldBeVerified.verified.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetDescription_HasValueDifferentValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetDescription_HasValueDifferentValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..6e33a528
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetDescription_HasValueDifferentValue_ShouldBeVerified.verified.txt
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetDescription_HasValueToNull_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetDescription_HasValueToNull_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..f140f3fe
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetDescription_HasValueToNull_ShouldBeVerified.verified.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetDescription_NoValueToValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetDescription_NoValueToValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..c81c416d
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetDescription_NoValueToValue_ShouldBeVerified.verified.txt
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetProperty_HasValueDifferentValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetProperty_HasValueDifferentValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..c5dc6618
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetProperty_HasValueDifferentValue_ShouldBeVerified.verified.txt
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetProperty_HasValueToNull_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetProperty_HasValueToNull_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..f140f3fe
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetProperty_HasValueToNull_ShouldBeVerified.verified.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetProperty_NoValueToValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetProperty_NoValueToValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..9a7ef2a8
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetProperty_NoValueToValue_ShouldBeVerified.verified.txt
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetRequiredValue_HasValueDifferentValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetRequiredValue_HasValueDifferentValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..daf66963
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetRequiredValue_HasValueDifferentValue_ShouldBeVerified.verified.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetRequiredValue_NoValueToValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetRequiredValue_NoValueToValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..c2b22a55
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetRequiredValue_NoValueToValue_ShouldBeVerified.verified.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetSelectorValue_HasValueDifferentValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetSelectorValue_HasValueDifferentValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..a27a8b12
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetSelectorValue_HasValueDifferentValue_ShouldBeVerified.verified.txt
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetSelectorValue_HasValueToNull_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetSelectorValue_HasValueToNull_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..e146580b
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetSelectorValue_HasValueToNull_ShouldBeVerified.verified.txt
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetSelectorValue_NoValueToValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetSelectorValue_NoValueToValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..df6962bf
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetSelectorValue_NoValueToValue_ShouldBeVerified.verified.txt
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetValue_ToEmptyHasValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetValue_ToEmptyHasValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..9c0c827c
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetValue_ToEmptyHasValue_ShouldBeVerified.verified.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetValue_ToNullHasValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetValue_ToNullHasValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..f140f3fe
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetValue_ToNullHasValue_ShouldBeVerified.verified.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetValue_ToValueEmptyValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetValue_ToValueEmptyValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..fc6a2e0c
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetValue_ToValueEmptyValue_ShouldBeVerified.verified.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetValue_ToValueFromNull_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetValue_ToValueFromNull_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..fc6a2e0c
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetValue_ToValueFromNull_ShouldBeVerified.verified.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.SetValue_ToValueHasValue_ShouldBeVerified.verified.txt b/tests/L5Sharp.Tests/LogixElementTests.SetValue_ToValueHasValue_ShouldBeVerified.verified.txt
new file mode 100644
index 00000000..fc6a2e0c
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.SetValue_ToValueHasValue_ShouldBeVerified.verified.txt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/tests/L5Sharp.Tests/LogixElementTests.cs b/tests/L5Sharp.Tests/LogixElementTests.cs
new file mode 100644
index 00000000..9f4f7f17
--- /dev/null
+++ b/tests/L5Sharp.Tests/LogixElementTests.cs
@@ -0,0 +1,1044 @@
+using System.Xml.Linq;
+using FluentAssertions;
+using L5Sharp.Enums;
+using L5Sharp.Utilities;
+
+// ReSharper disable UseObjectOrCollectionInitializer
+
+namespace L5Sharp.Tests;
+
+[TestFixture]
+public class LogixElementTests
+{
+ [OneTimeSetUp]
+ public void Setup()
+ {
+ LogixSerializer.Register();
+ LogixSerializer.Register();
+ }
+
+ [Test]
+ public void New_Default_ShouldNotBeNull()
+ {
+ var element = new TestElement();
+
+ element.Should().NotBeNull();
+ }
+
+ [Test]
+ public void New_Default_ShouldHaveExpectedValues()
+ {
+ var element = new TestElement();
+
+ element.L5XType.Should().Be("Test");
+ element.L5X.Should().BeNull();
+ element.IsAttached.Should().BeFalse();
+ element.Container.Should().BeEmpty();
+ element.Scope.Should().Be(Scope.Null);
+ }
+
+ [Test]
+ public void New_XElement_ShouldNotBeNull()
+ {
+ var element = new TestElement(new XElement("Test"));
+
+ element.Should().NotBeNull();
+ }
+
+ [Test]
+ public void New_NullXElement_ShouldThrowException()
+ {
+ FluentActions.Invoking(() => new TestElement(null!)).Should().Throw();
+ }
+
+ [Test]
+ public void Serialize_WhenCalled_ShouldNotBeNull()
+ {
+ var element = new TestElement();
+
+ var xml = element.Serialize();
+
+ xml.Should().NotBeNull();
+ }
+
+ [Test]
+ public void Serialize_WhenCalled_ShouldHaveExpectedElementName()
+ {
+ var element = new TestElement();
+
+ var xml = element.Serialize();
+
+ xml.Name.LocalName.Should().Be("Test");
+ }
+
+ [Test]
+ public void Clone_WhenCalled_ShouldNotBeNull()
+ {
+ var element = new TestElement();
+
+ var clone = element.Clone();
+
+ clone.Should().NotBeNull();
+ }
+
+ [Test]
+ public void Clone_WhenCalled_ShouldBeDifferentInstance()
+ {
+ var element = new TestElement();
+
+ var clone = element.Clone();
+
+ clone.Should().NotBeSameAs(element);
+ }
+
+ [Test]
+ public void Clone_WhenCalled_ShouldBeOfSameType()
+ {
+ var element = new TestElement();
+
+ var clone = element.Clone();
+
+ clone.Should().BeOfType();
+ }
+
+ [Test]
+ public void Clone_WhenCalled_ShouldBeDifferentXElementInstance()
+ {
+ var element = new TestElement();
+ var xml = element.Serialize();
+
+ var clone = element.Clone();
+ var copy = clone.Serialize();
+
+ xml.Should().NotBeSameAs(copy);
+ }
+
+ [Test]
+ public void Clone_ValidGenericParameter_ShouldNotBeNullAndOfSpecifiedType()
+ {
+ var element = new TestElement();
+
+ var clone = element.Clone();
+
+ clone.Should().NotBeNull();
+ clone.Should().BeOfType();
+ }
+
+ [Test]
+ public void GetValue_HasValue_ShouldHaveExpectedValue()
+ {
+ var xml = new XElement("Test", new XAttribute("OptionalValue", "Value"));
+ var element = new TestElement(xml);
+
+ var value = element.OptionalValue;
+
+ value.Should().Be("Value");
+ }
+
+ [Test]
+ public void GetValue_NoValue_ShouldBeNull()
+ {
+ var xml = new XElement("Test");
+ var element = new TestElement(xml);
+
+ var value = element.OptionalValue;
+
+ value.Should().BeNull();
+ }
+
+ [Test]
+ public Task SetValue_ToValueHasValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XAttribute("OptionalValue", "Value"));
+ var element = new TestElement(xml);
+
+ element.OptionalValue = "This is a test";
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetValue_ToValueEmptyValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XAttribute("OptionalValue", ""));
+ var element = new TestElement(xml);
+
+ element.OptionalValue = "This is a test";
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetValue_ToEmptyHasValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XAttribute("OptionalValue", "Value"));
+ var element = new TestElement(xml);
+
+ element.OptionalValue = string.Empty;
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetValue_ToValueFromNull_ShouldBeVerified()
+ {
+ var xml = new XElement("Test");
+ var element = new TestElement(xml);
+
+ element.OptionalValue = "This is a test";
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetValue_ToNullHasValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XAttribute("OptionalValue", "Value"));
+ var element = new TestElement(xml);
+
+ element.OptionalValue = null;
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public void GetSelectorValue_HasValue_ShouldBeExpectedValue()
+ {
+ var xml = new XElement("Test", new XElement("Child", new XAttribute("SelectorValue", "Value")));
+ var element = new TestElement(xml);
+
+ var value = element.SelectorValue;
+
+ value.Should().Be("Value");
+ }
+
+ [Test]
+ public void GetSelectorValue_NoValue_ShouldBeNull()
+ {
+ var xml = new XElement("Test", new XElement("Child"));
+ var element = new TestElement(xml);
+
+ var value = element.SelectorValue;
+
+ value.Should().BeNull();
+ }
+
+ [Test]
+ public Task SetSelectorValue_NoValueToValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XElement("Child"));
+ var element = new TestElement(xml);
+
+ element.SelectorValue = "Value";
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetSelectorValue_HasValueDifferentValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XElement("Child", new XAttribute("SelectorValue", "Value")));
+ var element = new TestElement(xml);
+
+ element.SelectorValue = "NewValue";
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetSelectorValue_HasValueToNull_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XElement("Child", new XAttribute("SelectorValue", "Value")));
+ var element = new TestElement(xml);
+
+ element.SelectorValue = null;
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public void GetChildValue_HasValue_ShouldBeExpectedValue()
+ {
+ var xml = new XElement("Test", new XElement("Child", new XAttribute("ChildValue", 123)));
+ var element = new TestElement(xml);
+
+ var value = element.ChildValue;
+
+ value.Should().Be(123);
+ }
+
+ [Test]
+ public void GetChildValue_NoValue_ShouldBeNull()
+ {
+ var xml = new XElement("Test", new XElement("Child"));
+ var element = new TestElement(xml);
+
+ var value = element.ChildValue;
+
+ value.Should().BeNull();
+ }
+
+ [Test]
+ public Task SetChildValue_NoValueToValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XElement("Child"));
+ var element = new TestElement(xml);
+
+ element.ChildValue = 123;
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetChildValue_HasValueDifferentValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XElement("Child", new XAttribute("ChildValue", 123)));
+ var element = new TestElement(xml);
+
+ element.ChildValue = 321;
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetChildValue_HasValueToNull_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XElement("Child", new XAttribute("ChildValue", 123)));
+ var element = new TestElement(xml);
+
+ element.ChildValue = null;
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public void GetRequiredValue_HasValue_ShouldBeExpectedValue()
+ {
+ var xml = new XElement("Test", new XAttribute("RequiredValue", "Value"));
+ var element = new TestElement(xml);
+
+ var value = element.RequiredValue;
+
+ value.Should().Be("Value");
+ }
+
+ [Test]
+ public void GetRequiredValue_NullValue_ShouldBeThrowException()
+ {
+ var xml = new XElement("Test");
+ var element = new TestElement(xml);
+
+ FluentActions.Invoking(() => element.RequiredValue).Should().Throw();
+ }
+
+ [Test]
+ public Task SetRequiredValue_NoValueToValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test");
+ var element = new TestElement(xml);
+
+ element.RequiredValue = "Value";
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetRequiredValue_HasValueDifferentValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XAttribute("RequiredValue", "Value"));
+ var element = new TestElement(xml);
+
+ element.RequiredValue = "NewValue";
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public void SetRequiredValue_HasValueToNull_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XAttribute("RequiredValue", "Value"));
+ var element = new TestElement(xml);
+
+ FluentActions.Invoking(() => element.RequiredValue = null!).Should().Throw();
+ }
+
+ [Test]
+ public void GetProperty_HasValue_ShouldBeExpectedValue()
+ {
+ var xml = new XElement("Test", new XElement("Property", "This is the value"));
+ var element = new TestElement(xml);
+
+ var value = element.Property;
+
+ value.Should().Be("This is the value");
+ }
+
+ [Test]
+ public void GetProperty_NoValue_ShouldBeNull()
+ {
+ var xml = new XElement("Test");
+ var element = new TestElement(xml);
+
+ var value = element.Property;
+
+ value.Should().BeNull();
+ }
+
+ [Test]
+ public Task SetProperty_NoValueToValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test");
+ var element = new TestElement(xml);
+
+ element.Property = "This is a new value";
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetProperty_HasValueDifferentValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XElement("Property", "This is the value"));
+ var element = new TestElement(xml);
+
+ element.Property = "This is the new value";
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetProperty_HasValueToNull_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XElement("Property", "This is the value"));
+ var element = new TestElement(xml);
+
+ element.Property = null;
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public void GetComplex_HasValue_ShouldBeExpectedValue()
+ {
+ var xml = new XElement("Test", new XElement("ChildElement", new XAttribute("SomeValue", "Value")));
+ var element = new TestElement(xml);
+
+ var value = element.ChildElement;
+
+ value.Should().NotBeNull();
+ value.Should().BeOfType();
+ value?.SomeValue.Should().Be("Value");
+ }
+
+ [Test]
+ public void GetComplex_NoValue_ShouldBeNull()
+ {
+ var xml = new XElement("Test");
+ var element = new TestElement(xml);
+
+ var value = element.ChildElement;
+
+ value.Should().BeNull();
+ }
+
+ [Test]
+ public Task SetComplex_NoValueToValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test");
+ var element = new TestElement(xml);
+
+ element.ChildElement = new ChildElement { SomeValue = "Test Value" };
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetComplex_HasValueDifferentValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XElement("ChildElement", new XAttribute("SomeValue", "Value")));
+ var element = new TestElement(xml);
+
+ element.ChildElement = new ChildElement { SomeValue = "Replacement object" };
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetComplex_HasValueToNull_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XElement("ChildElement", new XAttribute("SomeValue", "Value")));
+ var element = new TestElement(xml);
+
+ element.ChildElement = null;
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public void GetContainer_HasValue_ShouldBeExpectedValue()
+ {
+ var xml = new XElement("Test",
+ new XElement("ChildElements",
+ new XElement("ChildElement", new XAttribute("SomeValue", "Child_1")),
+ new XElement("ChildElement", new XAttribute("SomeValue", "Child_2")),
+ new XElement("ChildElement", new XAttribute("SomeValue", "Child_3"))
+ ));
+ var element = new TestElement(xml);
+
+ var value = element.ChildElements;
+
+ value.Should().NotBeNull();
+ value.Should().BeOfType>();
+ value.Should().HaveCount(3);
+ }
+
+ [Test]
+ public void GetContainer_NoContainerElement_ShouldThrowException()
+ {
+ var xml = new XElement("Test");
+ var element = new TestElement(xml);
+
+ FluentActions.Invoking(() => element.ChildElements).Should().Throw();
+ }
+
+ [Test]
+ public void GetContainer_NoElements_ShouldBeEmpty()
+ {
+ var xml = new XElement("Test", new XElement("ChildElements"));
+ var element = new TestElement(xml);
+
+ var value = element.ChildElements;
+
+ value.Should().BeEmpty();
+ }
+
+ [Test]
+ public Task SetContainer_NoValueToEmptyCollection_ShouldBeVerified()
+ {
+ var xml = new XElement("Test");
+ var element = new TestElement(xml);
+
+ element.ChildElements = new LogixContainer();
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetContainer_NoValueCollectionWithElements_ShouldBeVerified()
+ {
+ var xml = new XElement("Test");
+ var element = new TestElement(xml);
+
+ element.ChildElements = new LogixContainer
+ {
+ new() { SomeValue = "Child_1"},
+ new() { SomeValue = "Child_2"},
+ new() { SomeValue = "Child_3"}
+ };
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetContainer_HasValueDifferentValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test",
+ new XElement("ChildElements",
+ new XElement("ChildElement", new XAttribute("SomeValue", "Child_1")),
+ new XElement("ChildElement", new XAttribute("SomeValue", "Child_2")),
+ new XElement("ChildElement", new XAttribute("SomeValue", "Child_3"))
+ ));
+ var element = new TestElement(xml);
+
+ element.ChildElements = new LogixContainer
+ {
+ new() { SomeValue = "Child_3"},
+ new() { SomeValue = "Child_2"},
+ new() { SomeValue = "Child_1"}
+ };
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetContainer_HasValueToNull_ShouldBeVerified()
+ {
+ var xml = new XElement("Test",
+ new XElement("ChildElements",
+ new XElement("ChildElement", new XAttribute("SomeValue", "Child_1")),
+ new XElement("ChildElement", new XAttribute("SomeValue", "Child_2")),
+ new XElement("ChildElement", new XAttribute("SomeValue", "Child_3"))
+ ));
+ var element = new TestElement(xml);
+
+ element.ChildElements = null!;
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public void GetDateTime_HasValue_ShouldBeExpectedValue()
+ {
+ var xml = new XElement("Test", new XAttribute("Date", "Mon Sep 27 15:23:27 2021"));
+ var element = new TestElement(xml);
+
+ var value = element.Date;
+
+ value.Should().Be(new DateTime(2021, 9, 27, 15, 23, 27));
+ }
+
+ [Test]
+ public void GetDateTime_NoValue_ShouldBeNull()
+ {
+ var xml = new XElement("Test");
+ var element = new TestElement(xml);
+
+ var value = element.Date;
+
+ value.Should().BeNull();
+ }
+
+ [Test]
+ public Task SetDateTime_NoValueToValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test");
+ var element = new TestElement(xml);
+
+ element.Date = new DateTime(2021, 9, 27, 15, 23, 27);
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetDateTime_HasValueDifferentValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XAttribute("Date", "Mon Sep 27 15:23:27 2021"));
+ var element = new TestElement(xml);
+
+ element.Date = new DateTime(2023, 9, 27, 15, 23, 27);
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetDateTime_HasValueToNull_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XAttribute("Date", "Mon Sep 27 15:23:27 2021"));
+ var element = new TestElement(xml);
+
+ element.Date = null;
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetDescription_NoValueToValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test");
+ var element = new TestElement(xml);
+
+ element.Description = "This is a description test";
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetDescription_HasValueDifferentValue_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XElement("Description", new XCData("This is a test")));
+ var element = new TestElement(xml);
+
+ element.Description = "This is an updated description test";
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public Task SetDescription_HasValueToNull_ShouldBeVerified()
+ {
+ var xml = new XElement("Test", new XElement("Description", new XCData("This is a test")));
+ var element = new TestElement(xml);
+
+ element.Description = null;
+
+ return Verify(element.Serialize().ToString());
+ }
+
+ [Test]
+ public void AddAfter_Null_ShouldThrowException()
+ {
+ var xml = new XElement("Test", new XAttribute("RequiredValue", "Test_1"));
+ var element = new TestElement(xml);
+
+ FluentActions.Invoking(() => element.AddAfter(null!)).Should().Throw();
+ }
+
+ [Test]
+ public void AddAfter_NoParent_ShouldThrowException()
+ {
+ var xml = new XElement("Test", new XAttribute("RequiredValue", "Test_1"));
+ var element = new TestElement(xml);
+
+ FluentActions.Invoking(() => element.AddAfter(new TestElement { RequiredValue = "Test_3" })).Should()
+ .Throw();
+ }
+
+ [Test]
+ public Task AddAfter_ValidElement_ShouldBeVerified()
+ {
+ var container = new XElement("Container",
+ new XElement("Test", new XAttribute("RequiredValue", "Test_1")),
+ new XElement("Test", new XAttribute("RequiredValue", "Test_2"))
+ );
+ var xml = container.FirstNode as XElement;
+ var element = new TestElement(xml!);
+
+ element.AddAfter(new TestElement { RequiredValue = "Test_3" });
+
+ return Verify(container.ToString());
+ }
+
+ [Test]
+ public Task AddAfter_AlternateType_ShouldBeVerified()
+ {
+ var container = new XElement("Container",
+ new XElement("Test", new XAttribute("RequiredValue", "Test_1")),
+ new XElement("Test", new XAttribute("RequiredValue", "Test_2"))
+ );
+ var xml = container.FirstNode as XElement;
+ var element = new TestElement(xml!);
+
+ element.AddAfter(new TestElement(new XElement("Alternate", new XAttribute("RequiredValue", "Test_3"))));
+
+ return Verify(container.ToString());
+ }
+
+ [Test]
+ public void AddAfter_InvalidType_ShouldThrowException()
+ {
+ var container = new XElement("Container",
+ new XElement("Test", new XAttribute("RequiredValue", "Test_1")),
+ new XElement("Test", new XAttribute("RequiredValue", "Test_2"))
+ );
+ var xml = container.FirstNode as XElement;
+ var element = new TestElement(xml!);
+
+ FluentActions.Invoking(() => element.AddAfter(new ChildElement())).Should()
+ .Throw();
+ }
+
+ [Test]
+ public void AddBefore_Null_ShouldThrowException()
+ {
+ var xml = new XElement("Test", new XAttribute("RequiredValue", "Test_1"));
+ var element = new TestElement(xml);
+
+ FluentActions.Invoking(() => element.AddBefore(null!)).Should().Throw();
+ }
+
+ [Test]
+ public void AddBefore_NoParent_ShouldThrowException()
+ {
+ var xml = new XElement("Test", new XAttribute("RequiredValue", "Test_1"));
+ var element = new TestElement(xml);
+
+ FluentActions.Invoking(() => element.AddBefore(new TestElement { RequiredValue = "Test_3" })).Should()
+ .Throw();
+ }
+
+ [Test]
+ public Task AddBefore_ValidElement_ShouldBeVerified()
+ {
+ var container = new XElement("Container",
+ new XElement("Test", new XAttribute("RequiredValue", "Test_1")),
+ new XElement("Test", new XAttribute("RequiredValue", "Test_2"))
+ );
+ var xml = container.FirstNode as XElement;
+ var element = new TestElement(xml!);
+
+ element.AddBefore(new TestElement { RequiredValue = "Test_3" });
+
+ return Verify(container.ToString());
+ }
+
+ [Test]
+ public Task AddBefore_AlternateType_ShouldBeVerified()
+ {
+ var container = new XElement("Container",
+ new XElement("Test", new XAttribute("RequiredValue", "Test_1")),
+ new XElement("Test", new XAttribute("RequiredValue", "Test_2"))
+ );
+ var xml = container.FirstNode as XElement;
+ var element = new TestElement(xml!);
+
+ element.AddBefore(new TestElement(new XElement("Alternate", new XAttribute("RequiredValue", "Test_3"))));
+
+ return Verify(container.ToString());
+ }
+
+ [Test]
+ public void AddBefore_InvalidType_ShouldThrowException()
+ {
+ var container = new XElement("Container",
+ new XElement("Test", new XAttribute("RequiredValue", "Test_1")),
+ new XElement("Test", new XAttribute("RequiredValue", "Test_2"))
+ );
+ var xml = container.FirstNode as XElement;
+ var element = new TestElement(xml!);
+
+ FluentActions.Invoking(() => element.AddBefore(new ChildElement())).Should()
+ .Throw();
+ }
+
+ [Test]
+ public void Replace_Null_ShouldThrowException()
+ {
+ var xml = new XElement("Test", new XAttribute("RequiredValue", "Test_1"));
+ var element = new TestElement(xml);
+
+ FluentActions.Invoking(() => element.Replace(null!)).Should().Throw();
+ }
+
+ [Test]
+ public void Replace_NoParent_ShouldThrowException()
+ {
+ var xml = new XElement("Test", new XAttribute("RequiredValue", "Test_1"));
+ var element = new TestElement(xml);
+
+ FluentActions.Invoking(() => element.Replace(new TestElement { RequiredValue = "Test_3" })).Should()
+ .Throw();
+ }
+
+ [Test]
+ public Task Replace_ValidElement_ShouldBeVerified()
+ {
+ var container = new XElement("Container",
+ new XElement("Test", new XAttribute("RequiredValue", "Test_1")),
+ new XElement("Test", new XAttribute("RequiredValue", "Test_2"))
+ );
+ var xml = container.FirstNode as XElement;
+ var element = new TestElement(xml!);
+
+ element.Replace(new TestElement { RequiredValue = "Test_3" });
+
+ return Verify(container.ToString());
+ }
+
+ [Test]
+ public Task Replace_AlternateType_ShouldBeVerified()
+ {
+ var container = new XElement("Container",
+ new XElement("Test", new XAttribute("RequiredValue", "Test_1")),
+ new XElement("Test", new XAttribute("RequiredValue", "Test_2"))
+ );
+ var xml = container.FirstNode as XElement;
+ var element = new TestElement(xml!);
+
+ element.Replace(new TestElement(new XElement("Alternate", new XAttribute("RequiredValue", "Test_3"))));
+
+ return Verify(container.ToString());
+ }
+
+ [Test]
+ public void Replace_InvalidType_ShouldThrowException()
+ {
+ var container = new XElement("Container",
+ new XElement("Test", new XAttribute("RequiredValue", "Test_1")),
+ new XElement("Test", new XAttribute("RequiredValue", "Test_2"))
+ );
+ var xml = container.FirstNode as XElement;
+ var element = new TestElement(xml!);
+
+ FluentActions.Invoking(() => element.Replace(new ChildElement())).Should()
+ .Throw();
+ }
+
+ [Test]
+ public void Remove_NoParent_ShouldThrowException()
+ {
+ var xml = new XElement("Test", new XAttribute("RequiredValue", "Test_1"));
+ var element = new TestElement(xml);
+
+ FluentActions.Invoking(() => element.Remove()).Should()
+ .Throw();
+ }
+
+ [Test]
+ public Task Remove_HasParent_ShouldBeVerified()
+ {
+ var container = new XElement("Container",
+ new XElement("Test", new XAttribute("RequiredValue", "Test_1")),
+ new XElement("Test", new XAttribute("RequiredValue", "Test_2"))
+ );
+ var xml = container.FirstNode as XElement;
+ var element = new TestElement(xml!);
+
+ element.Remove();
+
+ return Verify(container.ToString());
+ }
+
+ [Test]
+ public void Convert_AsElementNullType_ShouldThrowException()
+ {
+ var element = new TestElement(new XElement("Test", new XAttribute("RequiredValue", "Test_1")));
+
+ FluentActions.Invoking(() => element.Convert(null!)).Should().Throw();
+ }
+
+ [Test]
+ public void Convert_AsElementEmptyType_ShouldThrowException()
+ {
+ var element = new TestElement(new XElement("Test", new XAttribute("RequiredValue", "Test_1")));
+
+ FluentActions.Invoking(() => element.Convert(string.Empty)).Should().Throw();
+ }
+
+ [Test]
+ public void Convert_AsElementInvalidType_ShouldThrowException()
+ {
+ var element = new TestElement(new XElement("Test", new XAttribute("RequiredValue", "Test_1")));
+
+ FluentActions.Invoking(() => element.Convert("ChildElement")).Should().Throw();
+ }
+
+ [Test]
+ public void Convert_AsElementAlternateType_ShouldHaveExpectedTypeAndBeDifferentInstance()
+ {
+ var element = new TestElement(new XElement("Test", new XAttribute("RequiredValue", "Test_1")));
+
+ var converted = element.Convert("Alternate");
+
+ converted.L5XType.Should().Be("Alternate");
+ converted.Should().BeOfType();
+ converted.Should().NotBeSameAs(element);
+ }
+
+ [Test]
+ public void Convert_AsElementSameType_ShouldHaveExpectedTypeAndBeSameInstance()
+ {
+ var element = new TestElement(new XElement("Test", new XAttribute("RequiredValue", "Test_1")));
+
+ var converted = element.Convert("Test");
+
+ converted.L5XType.Should().Be("Test");
+ converted.Should().BeOfType();
+ converted.Should().BeSameAs(element);
+ }
+
+ [Test]
+ public void Convert_AsTypeInvalidType_ShouldThrowException()
+ {
+ var element = new TestElement(new XElement("Test", new XAttribute("RequiredValue", "Test_1")));
+
+ FluentActions.Invoking(() => element.Convert()).Should().Throw();
+ }
+
+ [Test]
+ public void Convert_AsTypeAlternateType_ShouldHaveExpectedTypeAndBeDifferentInstance()
+ {
+ var element = new TestElement(new XElement("Test", new XAttribute("RequiredValue", "Test_1")));
+
+ var converted = element.Convert("Alternate");
+
+ converted.L5XType.Should().Be("Alternate");
+ converted.Should().BeOfType();
+ converted.Should().NotBeSameAs(element);
+ }
+
+ [Test]
+ public void Convert_AsTypeDefaultType_ShouldHaveExpectedTypeAndBeSameInstance()
+ {
+ var element = new TestElement(new XElement("Test", new XAttribute("RequiredValue", "Test_1")));
+
+ var converted = element.Convert();
+
+ converted.L5XType.Should().Be("Test");
+ converted.Should().BeOfType();
+ converted.Should().BeSameAs(element);
+ }
+}
+
+[L5XType("Test", "Container")]
+[L5XType("Alternate", "Container")]
+public class TestElement : LogixElement
+{
+ public TestElement()
+ {
+ }
+
+ public TestElement(XElement element) : base(element)
+ {
+ }
+
+ public string? OptionalValue
+ {
+ get => GetValue();
+ set => SetValue(value);
+ }
+
+ public string? SelectorValue
+ {
+ get => GetValue(e => e.Element("Child"));
+ set => SetValue(value, e => e.Element("Child"));
+ }
+
+ public string RequiredValue
+ {
+ get => GetRequiredValue();
+ set => SetRequiredValue(value);
+ }
+
+ public int? ChildValue
+ {
+ get => GetValue(XName.Get("Child"));
+ set => SetValue(value, XName.Get("Child"));
+ }
+
+ public string? Property
+ {
+ get => GetProperty();
+ set => SetProperty(value);
+ }
+
+ public string? Description
+ {
+ get => GetProperty();
+ set => SetDescription(value);
+ }
+
+ public DateTime? Date
+ {
+ get => GetDateTime();
+ set => SetDateTime(value);
+ }
+
+ public ChildElement? ChildElement
+ {
+ get => GetComplex();
+ set => SetComplex(value);
+ }
+
+ public LogixContainer ChildElements
+ {
+ get => GetContainer();
+ set => SetContainer(value);
+ }
+}
+
+public class ChildElement : LogixElement
+{
+ public ChildElement()
+ {
+ }
+
+ public ChildElement(XElement element) : base(element)
+ {
+ }
+
+ public string? SomeValue
+ {
+ get => GetValue();
+ set => SetValue(value);
+ }
+}
\ No newline at end of file