Skip to content

Commit 41ff388

Browse files
committed
Add support for summary/all generation modes & refactor models
- Introduce GenerationMode enum (Summary/All) for doc generation - Update ApiClassGenerator, CLI, and options to support mode param - Refactor models: add AllMode (ComponentInfo, etc.), move SummaryMode types - Remove old Mcp* models/tests; add new AllMode unit tests - Update McpDocumentationGenerator to use AllMode and always generate full docs - Add integration tests for output, file writing, and real FluentUI XML - Update solution file to include new test projects - Add GENERATION_MODES.md for usage and architecture documentation - Improve code organization and separation between summary/full logic
1 parent 5c28cda commit 41ff388

26 files changed

+1212
-179
lines changed

Microsoft.FluentUI-v5.sln

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{02EA
4343
EndProject
4444
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentUI.Samples.WasmStandalone", "examples\Samples\FluentUI.Samples.WasmStandalone\FluentUI.Samples.WasmStandalone.csproj", "{EB38AC75-966B-41BB-A1C5-0CAFE17FE3D5}"
4545
EndProject
46+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{91F72CE5-24E4-432C-A410-360A3C9C8591}"
47+
EndProject
48+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentUI.Demo.DocApiGen.IntegrationTests", "examples\Tools\FluentUI.Demo.DocApiGen.IntegrationTests\FluentUI.Demo.DocApiGen.IntegrationTests.csproj", "{E3369070-0374-60BB-075B-38A8D80AEA78}"
49+
EndProject
50+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentUI.Demo.DocApiGen.Tests", "examples\Tools\FluentUI.Demo.DocApiGen.Tests\FluentUI.Demo.DocApiGen.Tests.csproj", "{8A85FC00-B688-35AF-962C-2E07C4E02ED1}"
51+
EndProject
4652
Global
4753
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4854
Debug|Any CPU = Debug|Any CPU
@@ -98,6 +104,14 @@ Global
98104
{EB38AC75-966B-41BB-A1C5-0CAFE17FE3D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
99105
{EB38AC75-966B-41BB-A1C5-0CAFE17FE3D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
100106
{EB38AC75-966B-41BB-A1C5-0CAFE17FE3D5}.Release|Any CPU.Build.0 = Release|Any CPU
107+
{E3369070-0374-60BB-075B-38A8D80AEA78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
108+
{E3369070-0374-60BB-075B-38A8D80AEA78}.Debug|Any CPU.Build.0 = Debug|Any CPU
109+
{E3369070-0374-60BB-075B-38A8D80AEA78}.Release|Any CPU.ActiveCfg = Release|Any CPU
110+
{E3369070-0374-60BB-075B-38A8D80AEA78}.Release|Any CPU.Build.0 = Release|Any CPU
111+
{8A85FC00-B688-35AF-962C-2E07C4E02ED1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
112+
{8A85FC00-B688-35AF-962C-2E07C4E02ED1}.Debug|Any CPU.Build.0 = Debug|Any CPU
113+
{8A85FC00-B688-35AF-962C-2E07C4E02ED1}.Release|Any CPU.ActiveCfg = Release|Any CPU
114+
{8A85FC00-B688-35AF-962C-2E07C4E02ED1}.Release|Any CPU.Build.0 = Release|Any CPU
101115
EndGlobalSection
102116
GlobalSection(SolutionProperties) = preSolution
103117
HideSolutionNode = FALSE
@@ -112,13 +126,16 @@ Global
112126
{0252FA12-8398-4A68-8C80-8DFBDF3021FD} = {2A11833A-E392-484A-99DF-A870C0692302}
113127
{958BF092-4CF2-470C-B058-9244496B234F} = {B98A7516-E9B2-4301-B6A3-33656BF4F4D9}
114128
{B98A7516-E9B2-4301-B6A3-33656BF4F4D9} = {F273876F-7528-42B3-BFE8-7CFF8ED1E2A2}
115-
{2462C5CC-81BC-47AF-85B8-5FADD9E47ADF} = {B98A7516-E9B2-4301-B6A3-33656BF4F4D9}
129+
{2462C5CC-81BC-47AF-85B8-5FADD9E47ADF} = {91F72CE5-24E4-432C-A410-360A3C9C8591}
116130
{32466925-47C6-420F-B869-5F922162C3A7} = {B98A7516-E9B2-4301-B6A3-33656BF4F4D9}
117131
{E67B08B6-AEE4-4281-8700-1C87A5A3C11E} = {B98A7516-E9B2-4301-B6A3-33656BF4F4D9}
118132
{F380FA22-53D8-4381-B89B-4047AF544D53} = {A7EC98D2-21E3-4967-8C5A-D62E640305EB}
119133
{D52F6265-A983-46E0-8831-67FA80D95FBE} = {B98A7516-E9B2-4301-B6A3-33656BF4F4D9}
120134
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {F273876F-7528-42B3-BFE8-7CFF8ED1E2A2}
121135
{EB38AC75-966B-41BB-A1C5-0CAFE17FE3D5} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
136+
{91F72CE5-24E4-432C-A410-360A3C9C8591} = {B98A7516-E9B2-4301-B6A3-33656BF4F4D9}
137+
{E3369070-0374-60BB-075B-38A8D80AEA78} = {91F72CE5-24E4-432C-A410-360A3C9C8591}
138+
{8A85FC00-B688-35AF-962C-2E07C4E02ED1} = {91F72CE5-24E4-432C-A410-360A3C9C8591}
122139
EndGlobalSection
123140
GlobalSection(ExtensibilityGlobals) = postSolution
124141
SolutionGuid = {44D95FF7-AEBE-41FB-9D40-CF1E09ADC6BC}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<OutputType>Exe</OutputType>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<LangVersion>latest</LangVersion>
9+
<IsPackable>false</IsPackable>
10+
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="Microsoft.NET.Test.Sdk" />
15+
<PackageReference Include="xunit.v3" />
16+
<PackageReference Include="xunit.runner.visualstudio">
17+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
18+
<PrivateAssets>all</PrivateAssets>
19+
</PackageReference>
20+
<PackageReference Include="coverlet.msbuild">
21+
<PrivateAssets>all</PrivateAssets>
22+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
23+
</PackageReference>
24+
<PackageReference Include="coverlet.collector">
25+
<PrivateAssets>all</PrivateAssets>
26+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
27+
</PackageReference>
28+
<PackageReference Include="Microsoft.AspNetCore.Components.Web" />
29+
<PackageReference Include="LoxSmoke.DocXml" />
30+
</ItemGroup>
31+
32+
<ItemGroup>
33+
<ProjectReference Include="..\FluentUI.Demo.DocApiGen\FluentUI.Demo.DocApiGen.csproj" />
34+
</ItemGroup>
35+
36+
<!-- Include test assemblies for testing -->
37+
<ItemGroup>
38+
<None Update="TestData\**\*">
39+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
40+
</None>
41+
</ItemGroup>
42+
43+
</Project>
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
// ------------------------------------------------------------------------
2+
// This file is licensed to you under the MIT License.
3+
// ------------------------------------------------------------------------
4+
5+
using FluentUI.Demo.DocApiGen.Models;
6+
using System.Reflection;
7+
using System.Text.Json;
8+
using System.Linq;
9+
using Xunit;
10+
11+
namespace FluentUI.Demo.DocApiGen.IntegrationTests;
12+
13+
/// <summary>
14+
/// Integration tests using the real Microsoft.FluentUI.AspNetCore.Components.xml file.
15+
/// These tests validate documentation generation for actual FluentUI components with
16+
/// the ApiClassGenerator (Summary mode focus).
17+
/// Note: MCP (All mode) tests are skipped for now - will be implemented later.
18+
/// </summary>
19+
public class FluentUIComponentsIntegrationTests : IDisposable
20+
{
21+
private readonly FileInfo _xmlDocumentation;
22+
private readonly string _tempOutputDirectory;
23+
private readonly string _xmlPath;
24+
private readonly Assembly _fluentUIAssembly;
25+
26+
/// <summary>
27+
/// Initializes a new instance of the <see cref="FluentUIComponentsIntegrationTests"/> class.
28+
/// </summary>
29+
public FluentUIComponentsIntegrationTests()
30+
{
31+
_tempOutputDirectory = Path.Combine(Path.GetTempPath(), $"DocApiGen_FluentUI_Tests_{Guid.NewGuid()}");
32+
Directory.CreateDirectory(_tempOutputDirectory);
33+
34+
// Get project root directory
35+
var projectRoot = GetProjectRootDirectory();
36+
_xmlPath = Path.Combine(projectRoot, "examples", "Tools", "FluentUI.Demo.DocApiGen", "Microsoft.FluentUI.AspNetCore.Components.xml");
37+
38+
if (!File.Exists(_xmlPath))
39+
{
40+
throw new FileNotFoundException($"XML documentation file not found at: {_xmlPath}");
41+
}
42+
43+
_xmlDocumentation = new FileInfo(_xmlPath);
44+
45+
// Load the FluentUI assembly dynamically
46+
var fluentUIAssemblyPath = Path.Combine(projectRoot, "src", "Core", "bin", "Debug", "net9.0", "Microsoft.FluentUI.AspNetCore.Components.dll");
47+
48+
if (!File.Exists(fluentUIAssemblyPath))
49+
{
50+
// Try alternative path (Release build)
51+
fluentUIAssemblyPath = Path.Combine(projectRoot, "src", "Core", "bin", "Release", "net9.0", "Microsoft.FluentUI.AspNetCore.Components.dll");
52+
53+
if (!File.Exists(fluentUIAssemblyPath))
54+
{
55+
throw new FileNotFoundException($"FluentUI assembly not found. Please build the Core project first. Looked for: {fluentUIAssemblyPath}");
56+
}
57+
}
58+
59+
_fluentUIAssembly = Assembly.LoadFrom(fluentUIAssemblyPath);
60+
}
61+
62+
/// <summary>
63+
/// Gets the project root directory by walking up from the current directory.
64+
/// </summary>
65+
private static string GetProjectRootDirectory()
66+
{
67+
var directory = new DirectoryInfo(Directory.GetCurrentDirectory());
68+
69+
// Look for solution file
70+
while (directory != null)
71+
{
72+
var solutionFiles = directory.GetFiles("*.sln");
73+
if (solutionFiles.Length > 0)
74+
{
75+
return directory.FullName;
76+
}
77+
78+
directory = directory.Parent;
79+
}
80+
81+
throw new InvalidOperationException($"Could not find project root directory. Current directory: {Directory.GetCurrentDirectory()}");
82+
}
83+
84+
/// <summary>
85+
/// Cleanup temporary files and directories.
86+
/// </summary>
87+
public void Dispose()
88+
{
89+
if (Directory.Exists(_tempOutputDirectory))
90+
{
91+
try
92+
{
93+
Directory.Delete(_tempOutputDirectory, recursive: true);
94+
}
95+
catch
96+
{
97+
// Ignore cleanup errors
98+
}
99+
}
100+
}
101+
102+
#region ApiClassGenerator (Summary Mode) Tests
103+
104+
[Fact]
105+
public void ApiGenerator_ShouldGenerateJsonSuccessfully()
106+
{
107+
// Arrange
108+
var generator = new ApiClassGenerator(_fluentUIAssembly, _xmlDocumentation);
109+
110+
// Act
111+
var json = generator.GenerateJson(GenerationMode.Summary);
112+
113+
// Assert
114+
Assert.NotNull(json);
115+
Assert.NotEmpty(json);
116+
Assert.Contains("__Generated__", json);
117+
Assert.Contains("\"Mode\": \"Summary\"", json);
118+
}
119+
120+
[Fact]
121+
public void ApiGenerator_ShouldGenerateCSharpSuccessfully()
122+
{
123+
// Arrange
124+
var generator = new ApiClassGenerator(_fluentUIAssembly, _xmlDocumentation);
125+
126+
// Act
127+
var code = generator.GenerateCSharp(GenerationMode.Summary);
128+
129+
// Assert
130+
Assert.NotNull(code);
131+
Assert.NotEmpty(code);
132+
Assert.Contains("public class CodeComments", code);
133+
Assert.Contains("Mode: Summary", code);
134+
Assert.Contains("SummaryData", code);
135+
}
136+
137+
[Fact]
138+
public void ApiGenerator_JsonOutput_ShouldContainFluentUIComponents()
139+
{
140+
// Arrange
141+
var generator = new ApiClassGenerator(_fluentUIAssembly, _xmlDocumentation);
142+
143+
// Act
144+
var json = generator.GenerateJson(GenerationMode.Summary);
145+
146+
// Assert
147+
// Should contain common FluentUI component names
148+
Assert.Contains("FluentButton", json);
149+
}
150+
151+
[Fact]
152+
public void ApiGenerator_CSharpOutput_ShouldContainFluentUIComponents()
153+
{
154+
// Arrange
155+
var generator = new ApiClassGenerator(_fluentUIAssembly, _xmlDocumentation);
156+
157+
// Act
158+
var code = generator.GenerateCSharp(GenerationMode.Summary);
159+
160+
// Assert
161+
// Should contain common FluentUI component names
162+
Assert.Contains("FluentButton", code);
163+
}
164+
165+
[Fact]
166+
public void ApiGenerator_JsonOutput_ShouldBeValidJson()
167+
{
168+
// Arrange
169+
var generator = new ApiClassGenerator(_fluentUIAssembly, _xmlDocumentation);
170+
171+
// Act
172+
var json = generator.GenerateJson(GenerationMode.Summary);
173+
174+
// Assert - Verify it's valid JSON
175+
var exception = Record.Exception(() => JsonDocument.Parse(json));
176+
Assert.Null(exception);
177+
}
178+
179+
[Fact]
180+
public void ApiGenerator_SaveToFile_JsonShouldSucceed()
181+
{
182+
// Arrange
183+
var generator = new ApiClassGenerator(_fluentUIAssembly, _xmlDocumentation);
184+
var outputPath = Path.Combine(_tempOutputDirectory, "fluentui_summary.json");
185+
186+
// Act
187+
generator.SaveToFile(outputPath, "json", GenerationMode.Summary);
188+
189+
// Assert
190+
Assert.True(File.Exists(outputPath));
191+
192+
var content = File.ReadAllText(outputPath);
193+
Assert.NotEmpty(content);
194+
Assert.Contains("__Generated__", content);
195+
196+
// Verify valid JSON
197+
var exception = Record.Exception(() => JsonDocument.Parse(content));
198+
Assert.Null(exception);
199+
}
200+
201+
[Fact]
202+
public void ApiGenerator_SaveToFile_CSharpShouldSucceed()
203+
{
204+
// Arrange
205+
var generator = new ApiClassGenerator(_fluentUIAssembly, _xmlDocumentation);
206+
var outputPath = Path.Combine(_tempOutputDirectory, "fluentui_summary.cs");
207+
208+
// Act
209+
generator.SaveToFile(outputPath, "csharp", GenerationMode.Summary);
210+
211+
// Assert
212+
Assert.True(File.Exists(outputPath));
213+
214+
var content = File.ReadAllText(outputPath);
215+
Assert.NotEmpty(content);
216+
Assert.Contains("public class CodeComments", content);
217+
}
218+
219+
[Fact]
220+
public void ApiGenerator_LargeScale_ShouldCompleteWithoutErrors()
221+
{
222+
// Arrange
223+
var generator = new ApiClassGenerator(_fluentUIAssembly, _xmlDocumentation);
224+
225+
// Act & Assert - Should complete without throwing
226+
var exception = Record.Exception(() =>
227+
{
228+
var json = generator.GenerateJson(GenerationMode.Summary);
229+
Assert.NotNull(json);
230+
Assert.NotEmpty(json);
231+
232+
var code = generator.GenerateCSharp(GenerationMode.Summary);
233+
Assert.NotNull(code);
234+
Assert.NotEmpty(code);
235+
});
236+
237+
Assert.Null(exception);
238+
}
239+
240+
[Fact]
241+
public void ApiGenerator_OutputSize_ShouldBeReasonable()
242+
{
243+
// Arrange
244+
var generator = new ApiClassGenerator(_fluentUIAssembly, _xmlDocumentation);
245+
246+
// Act
247+
var json = generator.GenerateJson(GenerationMode.Summary);
248+
var code = generator.GenerateCSharp(GenerationMode.Summary);
249+
250+
// Assert - Output should be substantial but not excessive
251+
Assert.True(json.Length > 1000, "JSON output should be substantial");
252+
Assert.True(json.Length < 50_000_000, "JSON output should not be excessive");
253+
254+
Assert.True(code.Length > 1000, "C# output should be substantial");
255+
Assert.True(code.Length < 50_000_000, "C# output should not be excessive");
256+
}
257+
258+
[Fact]
259+
public void ApiGenerator_JsonMetadata_ShouldBePresent()
260+
{
261+
// Arrange
262+
var generator = new ApiClassGenerator(_fluentUIAssembly, _xmlDocumentation);
263+
264+
// Act
265+
var json = generator.GenerateJson(GenerationMode.Summary);
266+
using var doc = JsonDocument.Parse(json);
267+
268+
// Assert
269+
var root = doc.RootElement;
270+
Assert.True(root.TryGetProperty("__Generated__", out var generated));
271+
272+
var generatedObj = generated;
273+
Assert.True(generatedObj.TryGetProperty("AssemblyVersion", out _));
274+
Assert.True(generatedObj.TryGetProperty("DateUtc", out _));
275+
Assert.True(generatedObj.TryGetProperty("Mode", out var mode));
276+
Assert.Equal("Summary", mode.GetString());
277+
}
278+
279+
#endregion
280+
281+
#region MCP Tests (Skipped - Future Implementation)
282+
283+
// Note: MCP/All mode tests are commented out for now
284+
// These will be implemented in a future phase
285+
286+
/*
287+
[Fact(Skip = "MCP implementation deferred")]
288+
public void McpGenerator_WillBeImplementedLater()
289+
{
290+
// MCP tests will be added when ready to work on All mode
291+
Assert.True(true);
292+
}
293+
*/
294+
295+
#endregion
296+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// ------------------------------------------------------------------------
2+
// This file is licensed to you under the MIT License.
3+
// ------------------------------------------------------------------------
4+
5+
global using Xunit;
6+
global using System;
7+
global using System.IO;

0 commit comments

Comments
 (0)