dotnet / docfx

Static site generator for .NET API documentation.
https://dotnet.github.io/docfx/
MIT License
3.94k stars 840 forks source link

feat: Add `categoryLayout` option for metadata generation #9965

Closed filzrev closed 1 month ago

filzrev commented 1 month ago

This PR intended to fix #9934 feature request issue by adding new categoryLayout option.

Description This PR add following options to docfx metadata config.


categoryLayout

Specifies how categories in TOC are organized:

[!NOTE] This setting is valid when using apiPage or markdown output format. mref format don't support categories.


Unit Tests It's manually tested by using docfx\samples\seed docs with both samePage / separatedPages options. And it pass following unit tests. (It require slow code analysis operation. So it's excluded from this commit)

MetadataCommandApiPageTocCategoryTest.cs
```csharp // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Docfx.Common; using Docfx.DataContracts.Common; using Docfx.Dotnet; using Docfx.Tests.Common; using FluentAssertions; namespace Docfx.Tests; public partial class MetadataCommandApiPageTocCategoryTest : TestBase { /// /// Use MetadataCommand to generate YAML files from a c# project and a VB project separately /// private readonly string _outputFolder; private readonly string _projectFolder; public MetadataCommandApiPageTocCategoryTest() { _outputFolder = GetRandomFolder(); _projectFolder = GetRandomFolder(); } [Fact] [Trait("Related", "docfx")] public async Task TestSeparatedPagesTocWithDefaulCategories() { var projectFile = Path.Combine(_projectFolder, "test.csproj"); var sourceFile = Path.Combine(_projectFolder, "test.cs"); File.Copy("Assets/test.csproj.sample.1", projectFile); File.Copy("Assets/test-multinamespace.cs.sample.1", sourceFile); await DotnetApiCatalog.Exec( new(new MetadataJsonItemConfig { Dest = _outputFolder, Src = new(new FileMappingItem(projectFile)) { Expanded = true }, NamespaceLayout = NamespaceLayout.Nested, CategoryLayout = CategoryLayout.Flattened, MemberLayout = MemberLayout.SeparatePages, OutputFormat = MetadataOutputFormat.ApiPage, }), new(), Directory.GetCurrentDirectory()); var file = Path.Combine(_outputFolder, "toc.yml"); Assert.True(File.Exists(file)); var tocViewModel = YamlUtility.Deserialize(file); var expected = YamlUtility.Deserialize(new StringReader(ExpectedData.Default)); // Assert tocViewModel.Should().BeEquivalentTo(expected); } [Fact] [Trait("Related", "docfx")] public async Task TestSeparatedPagesTocWithNestedCategories() { var projectFile = Path.Combine(_projectFolder, "test.csproj"); var sourceFile = Path.Combine(_projectFolder, "test.cs"); File.Copy("Assets/test.csproj.sample.1", projectFile); File.Copy("Assets/test-multinamespace.cs.sample.1", sourceFile); await DotnetApiCatalog.Exec( new(new MetadataJsonItemConfig { Dest = _outputFolder, Src = new(new FileMappingItem(projectFile)) { Expanded = true }, NamespaceLayout = NamespaceLayout.Nested, CategoryLayout = CategoryLayout.Nested, MemberLayout = MemberLayout.SeparatePages, OutputFormat = MetadataOutputFormat.ApiPage, }), new(), Directory.GetCurrentDirectory()); var file = Path.Combine(_outputFolder, "toc.yml"); Assert.True(File.Exists(file)); var tocViewModel = YamlUtility.Deserialize(file); var expected = YamlUtility.Deserialize(new StringReader(ExpectedData.Nested)); // Assert tocViewModel.Should().BeEquivalentTo(expected); } [Fact] [Trait("Related", "docfx")] public async Task TestSeparatedPagesTocWithNoneCategories() { var projectFile = Path.Combine(_projectFolder, "test.csproj"); var sourceFile = Path.Combine(_projectFolder, "test.cs"); File.Copy("Assets/test.csproj.sample.1", projectFile); File.Copy("Assets/test-multinamespace.cs.sample.1", sourceFile); await DotnetApiCatalog.Exec( new(new MetadataJsonItemConfig { Dest = _outputFolder, Src = new(new FileMappingItem(projectFile)) { Expanded = true }, NamespaceLayout = NamespaceLayout.Nested, CategoryLayout = CategoryLayout.None, MemberLayout = MemberLayout.SeparatePages, OutputFormat = MetadataOutputFormat.ApiPage, }), new(), Directory.GetCurrentDirectory()); var file = Path.Combine(_outputFolder, "toc.yml"); Assert.True(File.Exists(file)); var tocViewModel = YamlUtility.Deserialize(file); var expected = YamlUtility.Deserialize(new StringReader(ExpectedData.None)); // Assert tocViewModel.Should().BeEquivalentTo(expected); } private static class ExpectedData { public static readonly string Default = @" - name: OtherNamespace href: OtherNamespace.yml items: - name: Classes - name: OtherBar href: OtherNamespace.OtherBar.yml items: - name: Methods - name: FooBar href: OtherNamespace.OtherBar.FooBar.yml - name: Samples href: Samples.yml items: - name: Foo href: Samples.Foo.yml items: - name: Sub href: Samples.Foo.Sub.yml items: - name: Classes - name: SubBar href: Samples.Foo.Sub.SubBar.yml items: - name: Methods - name: FooBar href: Samples.Foo.Sub.SubBar.FooBar.yml - name: Classes - name: Bar href: Samples.Foo.Bar.yml items: - name: Methods - name: FooBar href: Samples.Foo.Bar.FooBar.yml ".Trim(); public static readonly string Nested = @" - name: OtherNamespace href: OtherNamespace.yml items: - name: Classes items: - name: OtherBar href: OtherNamespace.OtherBar.yml items: - name: Methods items: - name: FooBar href: OtherNamespace.OtherBar.FooBar.yml - name: Samples href: Samples.yml items: - name: Foo href: Samples.Foo.yml items: - name: Sub href: Samples.Foo.Sub.yml items: - name: Classes items: - name: SubBar href: Samples.Foo.Sub.SubBar.yml items: - name: Methods items: - name: FooBar href: Samples.Foo.Sub.SubBar.FooBar.yml - name: Classes items: - name: Bar href: Samples.Foo.Bar.yml items: - name: Methods items: - name: FooBar href: Samples.Foo.Bar.FooBar.yml ".Trim(); public static readonly string None = @" - name: OtherNamespace href: OtherNamespace.yml items: - name: OtherBar href: OtherNamespace.OtherBar.yml items: - name: FooBar href: OtherNamespace.OtherBar.FooBar.yml - name: Samples href: Samples.yml items: - name: Foo href: Samples.Foo.yml items: - name: Sub href: Samples.Foo.Sub.yml items: - name: SubBar href: Samples.Foo.Sub.SubBar.yml items: - name: FooBar href: Samples.Foo.Sub.SubBar.FooBar.yml - name: Bar href: Samples.Foo.Bar.yml items: - name: FooBar href: Samples.Foo.Bar.FooBar.yml ".Trim(); } } ```
codecov[bot] commented 1 month ago

Codecov Report

Attention: Patch coverage is 50.00000% with 10 lines in your changes are missing coverage. Please review.

Project coverage is 78.90%. Comparing base (fe673ec) to head (64af69e). Report is 179 commits behind head on main.

Files Patch % Lines
src/Docfx.Dotnet/DotnetApiCatalog.Toc.cs 40.00% 8 Missing and 1 partial :warning:
src/docfx/Models/MetadataCommand.cs 0.00% 0 Missing and 1 partial :warning:
Additional details and impacted files ```diff @@ Coverage Diff @@ ## main #9965 +/- ## ========================================== + Coverage 74.31% 78.90% +4.59% ========================================== Files 536 540 +4 Lines 23189 23428 +239 Branches 4056 4056 ========================================== + Hits 17234 18487 +1253 + Misses 4853 3804 -1049 - Partials 1102 1137 +35 ```

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.