Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions csharp/Platform.Scopes.Tests/XmlDocumentationIncludeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
using System;
using System.IO;
using System.Reflection;
using System.Xml;
using Xunit;

namespace Platform.Scopes.Tests
{
/// <summary>
/// Tests to verify that XML documentation include tags are working correctly.
/// </summary>
public class XmlDocumentationIncludeTests
{
private const string ExpectedScopeClassSummary = "Represents a dependency injection scope that manages object dependencies and their lifecycles.";
private const string ExpectedUseMethodSummary = "Resolves and returns an instance of the specified type T, automatically managing its dependencies.";
private const string ExpectedConstructorSummary = "Initializes a new instance of the";

[Fact]
public void XmlDocumentationFile_ShouldExist()
{
// Arrange
var assemblyLocation = Assembly.GetAssembly(typeof(Scope))?.Location;
Assert.NotNull(assemblyLocation);

var xmlDocPath = Path.ChangeExtension(assemblyLocation, ".xml");

// Act & Assert
Assert.True(File.Exists(xmlDocPath), $"XML documentation file should exist at: {xmlDocPath}");
}

[Fact]
public void ScopeClass_ShouldHaveIncludedDocumentation()
{
// Arrange
var xmlDoc = LoadXmlDocumentation();

// Act
var scopeClassNode = xmlDoc.SelectSingleNode("//member[@name='T:Platform.Scopes.Scope']/summary");

// Assert
Assert.NotNull(scopeClassNode);
var summaryText = scopeClassNode.InnerText.Trim();
Assert.Contains(ExpectedScopeClassSummary, summaryText);

// Verify that the included documentation contains the expected detailed description
var remarksNode = xmlDoc.SelectSingleNode("//member[@name='T:Platform.Scopes.Scope']/remarks");
Assert.NotNull(remarksNode);
var remarksText = remarksNode.InnerText.Trim();
Assert.Contains("The Scope class is the core component of the Platform.Scopes library", remarksText);
Assert.Contains("Automatic dependency resolution", remarksText);
}

[Fact]
public void ScopeConstructor_ShouldHaveIncludedDocumentation()
{
// Arrange
var xmlDoc = LoadXmlDocumentation();

// Act
var constructorNode = xmlDoc.SelectSingleNode("//member[@name='M:Platform.Scopes.Scope.#ctor(System.Boolean,System.Boolean)']/summary");

// Assert
Assert.NotNull(constructorNode);
var summaryText = constructorNode.InnerText.Trim();
Assert.Contains(ExpectedConstructorSummary, summaryText);
Assert.Contains("auto-include and auto-explore settings", summaryText);

// Verify parameter documentation is included
var autoIncludeParam = xmlDoc.SelectSingleNode("//member[@name='M:Platform.Scopes.Scope.#ctor(System.Boolean,System.Boolean)']/param[@name='autoInclude']");
Assert.NotNull(autoIncludeParam);
Assert.Contains("automatically include types in the scope", autoIncludeParam.InnerText);

var autoExploreParam = xmlDoc.SelectSingleNode("//member[@name='M:Platform.Scopes.Scope.#ctor(System.Boolean,System.Boolean)']/param[@name='autoExplore']");
Assert.NotNull(autoExploreParam);
Assert.Contains("automatically explore and include assemblies", autoExploreParam.InnerText);
}

[Fact]
public void UseMethod_ShouldHaveIncludedDocumentation()
{
// Arrange
var xmlDoc = LoadXmlDocumentation();

// Act
var useMethodNode = xmlDoc.SelectSingleNode("//member[@name='M:Platform.Scopes.Scope.Use``1']/summary");

// Assert
Assert.NotNull(useMethodNode);
var summaryText = useMethodNode.InnerText.Trim();
Assert.Contains(ExpectedUseMethodSummary, summaryText);

// Verify return documentation
var returnsNode = xmlDoc.SelectSingleNode("//member[@name='M:Platform.Scopes.Scope.Use``1']/returns");
Assert.NotNull(returnsNode);
Assert.Contains("An instance of type T with all dependencies resolved", returnsNode.InnerText);

// Verify exception documentation
var exceptionNode = xmlDoc.SelectSingleNode("//member[@name='M:Platform.Scopes.Scope.Use``1']/exception[@cref='T:System.InvalidOperationException']");
Assert.NotNull(exceptionNode);
Assert.Contains("Thrown when the type T is excluded", exceptionNode.InnerText);

// Verify detailed remarks are included
var remarksNode = xmlDoc.SelectSingleNode("//member[@name='M:Platform.Scopes.Scope.Use``1']/remarks");
Assert.NotNull(remarksNode);
var remarksText = remarksNode.InnerText.Trim();
Assert.Contains("This method is the primary entry point for dependency resolution", remarksText);
Assert.Contains("Check if the type is excluded", remarksText);
}

[Fact]
public void IncludeTag_ShouldReferenceCorrectExternalFile()
{
// Arrange
var sourceFilePath = GetScopeSourceFilePath();
var sourceContent = File.ReadAllText(sourceFilePath);

// Act & Assert
Assert.Contains("include file='Documentation.xml'", sourceContent);
Assert.Contains("path='docs/members[@name=\"Scope\"]/Scope/*'", sourceContent);
Assert.Contains("path='docs/members[@name=\"Scope\"]/constructor/*'", sourceContent);
Assert.Contains("path='docs/members[@name=\"Scope\"]/Use/*'", sourceContent);
}

[Fact]
public void ExternalDocumentationFile_ShouldExist()
{
// Arrange
var sourceDirectory = Path.GetDirectoryName(GetScopeSourceFilePath());
var documentationPath = Path.Combine(sourceDirectory!, "Documentation.xml");

// Act & Assert
Assert.True(File.Exists(documentationPath), $"External documentation file should exist at: {documentationPath}");

// Verify the external file has valid XML structure
var xmlDoc = new XmlDocument();
xmlDoc.Load(documentationPath);

var scopeNode = xmlDoc.SelectSingleNode("//members[@name='Scope']/Scope");
Assert.NotNull(scopeNode);

var constructorNode = xmlDoc.SelectSingleNode("//members[@name='Scope']/constructor");
Assert.NotNull(constructorNode);

var useNode = xmlDoc.SelectSingleNode("//members[@name='Scope']/Use");
Assert.NotNull(useNode);
}

private static XmlDocument LoadXmlDocumentation()
{
var assemblyLocation = Assembly.GetAssembly(typeof(Scope))?.Location;
var xmlDocPath = Path.ChangeExtension(assemblyLocation, ".xml");

var xmlDoc = new XmlDocument();
xmlDoc.Load(xmlDocPath);
return xmlDoc;
}

private static string GetScopeSourceFilePath()
{
// Navigate to the source file from test assembly location
var testAssemblyLocation = Assembly.GetExecutingAssembly().Location;
var testDirectory = Path.GetDirectoryName(testAssemblyLocation);
var sourceDirectory = Path.GetFullPath(Path.Combine(testDirectory!, "..", "..", "..", "..", "Platform.Scopes"));
return Path.Combine(sourceDirectory, "Scope.cs");
}
}
}
80 changes: 80 additions & 0 deletions csharp/Platform.Scopes/Documentation.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8" ?>
<docs>
<members name="Scope">
<Scope>
<summary>
<para>
Represents a dependency injection scope that manages object dependencies and their lifecycles.
This scope provides mechanisms for including and excluding types, resolving dependencies automatically,
and maintaining clean separation of concerns in dependency management.
</para>
<para></para>
</summary>
<remarks>
<para>
The Scope class is the core component of the Platform.Scopes library, providing dependency injection
capabilities with automatic resolution, type inclusion/exclusion, and lifecycle management.
</para>
<para>
Key features:
- Automatic dependency resolution
- Type inclusion and exclusion mechanisms
- Constructor injection support
- Singleton pattern support
- Automatic disposal of managed dependencies
</para>
</remarks>
</Scope>
<constructor>
<summary>
<para>
Initializes a new instance of the <see cref="Scope"/> class with specified auto-include and auto-explore settings.
</para>
<para></para>
</summary>
<param name="autoInclude">
<para>
A value indicating whether to automatically include types in the scope when they are used.
When true, using a type will automatically add it to the included types set.
</para>
<para></para>
</param>
<param name="autoExplore">
<para>
A value indicating whether to automatically explore and include assemblies when resolving types.
When true, the scope will automatically include assemblies containing required types during resolution.
</para>
<para></para>
</param>
</constructor>
<Use>
<summary>
<para>
Resolves and returns an instance of the specified type T, automatically managing its dependencies.
</para>
<para></para>
</summary>
<typeparam name="T">
<para>The type to resolve and return.</para>
<para></para>
</typeparam>
<returns>
<para>An instance of type T with all dependencies resolved.</para>
<para></para>
</returns>
<exception cref="InvalidOperationException">
Thrown when the type T is excluded from the scope or when dependency resolution fails.
</exception>
<remarks>
<para>
This method is the primary entry point for dependency resolution. It will:
1. Check if the type is excluded and throw an exception if so
2. Optionally auto-include the type if autoInclude is enabled
3. Attempt to resolve the type and all its dependencies
4. Add the resolved instance to the dependency stack
5. Return the fully constructed instance
</para>
</remarks>
</Use>
</members>
</docs>
23 changes: 3 additions & 20 deletions csharp/Platform.Scopes/Scope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,7 @@

namespace Platform.Scopes
{
/// <summary>
/// <para>
/// Represents the scope.
/// </para>
/// <para></para>
/// </summary>
/// <include file='Documentation.xml' path='docs/members[@name="Scope"]/Scope/*' />
/// <seealso cref="DisposableBase"/>
public class Scope : DisposableBase
{
Expand All @@ -38,20 +33,7 @@ public class Scope : DisposableBase
private readonly HashSet<object> _blocked = new HashSet<object>();
private readonly Dictionary<Type, object> _resolutions = new Dictionary<Type, object>();

/// <summary>
/// <para>
/// Initializes a new <see cref="Scope"/> instance.
/// </para>
/// <para></para>
/// </summary>
/// <param name="autoInclude">
/// <para>A auto include.</para>
/// <para></para>
/// </param>
/// <param name="autoExplore">
/// <para>A auto explore.</para>
/// <para></para>
/// </param>
/// <include file='Documentation.xml' path='docs/members[@name="Scope"]/constructor/*' />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Scope(bool autoInclude, bool autoExplore)
{
Expand Down Expand Up @@ -247,6 +229,7 @@ public void Include(object @object)

#region Use

/// <include file='Documentation.xml' path='docs/members[@name="Scope"]/Use/*' />
/// <remarks>
/// TODO: Use Default[T].Instance if the only constructor object has is parameterless.
/// TODO: Think of interface chaining IDoubletLinks[T] (default) -> IDoubletLinks[T] (checker) -> IDoubletLinks[T] (synchronizer) (may be UseChain[IDoubletLinks[T], Types[DefaultLinks, DefaultLinksDependencyChecker, DefaultSynchronizedLinks]]
Expand Down
Loading