feat: Add `categoryLayout` option for metadata generation by filzrev · Pull Request #9965 · dotnet/docfx

// 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
{
    /// <summary>
    /// Use MetadataCommand to generate YAML files from a c# project and a VB project separately
    /// </summary>
    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<TocItemViewModel[]>(file);

        var expected = YamlUtility.Deserialize<TocItemViewModel[]>(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<TocItemViewModel[]>(file);

        var expected = YamlUtility.Deserialize<TocItemViewModel[]>(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<TocItemViewModel[]>(file);

        var expected = YamlUtility.Deserialize<TocItemViewModel[]>(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<TArg>
      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<TArg>
          href: Samples.Foo.Sub.SubBar.FooBar.yml
    - name: Classes
    - name: Bar
      href: Samples.Foo.Bar.yml
      items:
      - name: Methods
      - name: FooBar<TArg>
        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<TArg>
          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<TArg>
              href: Samples.Foo.Sub.SubBar.FooBar.yml
    - name: Classes
      items:
      - name: Bar
        href: Samples.Foo.Bar.yml
        items:
        - name: Methods
          items:
          - name: FooBar<TArg>
            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<TArg>
      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<TArg>
          href: Samples.Foo.Sub.SubBar.FooBar.yml
    - name: Bar
      href: Samples.Foo.Bar.yml
      items:
      - name: FooBar<TArg>
        href: Samples.Foo.Bar.FooBar.yml
".Trim();
    }
}