Skip to content
Merged
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
2 changes: 2 additions & 0 deletions LICENSING.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ The website MIT grant does not replace any dependency's license.
| --- | --- |
| `Redot-Documentation/wwwroot/lib/bootstrap/` (Bootstrap 5.3.3) | MIT, Copyright (c) 2011-2024 The Bootstrap Authors; [full text](Redot-Documentation/wwwroot/lib/bootstrap/LICENSE.txt) |
| `Redot-Documentation/wwwroot/lib/prism/` (Prism) | MIT, Copyright (c) 2012 Lea Verou; [full text](Redot-Documentation/wwwroot/lib/prism/LICENSE.txt) |
| `Redot-Documentation/wwwroot/fonts/geist/` (Geist) | SIL OFL 1.1, Copyright 2024 The Geist Project Authors; [full text](Redot-Documentation/wwwroot/fonts/geist/OFL.txt) |
| `Redot-Documentation/wwwroot/fonts/jetbrains-mono/` (JetBrains Mono) | SIL OFL 1.1, Copyright 2020 The JetBrains Mono Project Authors; [full text](Redot-Documentation/wwwroot/fonts/jetbrains-mono/OFL.txt) |
| `Redot-Documentation/wwwroot/Icons/Font-Awesome/` | Font Awesome Free: icons CC BY 4.0, fonts SIL OFL 1.1, code MIT, as applicable; [original notice](Redot-Documentation/wwwroot/Icons/Font-Awesome/LICENSE.txt) |
| MudBlazor 9.9.0 | MIT; [full notice](Redot-Documentation/wwwroot/licenses/MUDBLAZOR-MIT.txt) |
| Markdig 0.41.3 | BSD-2-Clause, Copyright (c) 2018-2019 Alexandre Mutel; [full text](Redot-Documentation/wwwroot/licenses/MARKDIG-BSD-2-CLAUSE.txt), retrieved from the package's source revision `7ff8db9016593b71f9ae17d9b2b053fbd54e9cdf` |
Expand Down
75 changes: 73 additions & 2 deletions Redot-Documentation-Tests/ClassDocumentationComponentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,33 @@ public async Task Home_LinksToExistingStableDocumentation()
Assert.Empty(renderer.Errors);
}

[Theory]
[InlineData("/en/26.1/Tutorials/example")]
[InlineData("/en/26.1/Classes")]
public async Task MainLayout_RegistersDockedDrawerWhenNavigatingFromHome(string destination)
{
await using var services = CreateServices();
var navigation = services.GetRequiredService<NavigationManager>();
navigation.NavigateTo("/");
await using var renderer = new TestRenderer(services);
int id = await renderer.Dispatcher.InvokeAsync(() => renderer.RenderAsync(typeof(MainLayout), ParameterView.Empty));

for (int visit = 0; visit < 2; visit++)
{
await renderer.Dispatcher.InvokeAsync(() => navigation.NavigateTo(destination));
Assert.Empty(renderer.Errors);
string[] classes = await renderer.Dispatcher.InvokeAsync(() => renderer.CssClasses(id).ToArray());
Assert.True(classes.Contains("mud-drawer-open-responsive-md-left"),
$"Visit {visit}: {string.Join(' ', classes.Where(css => css.StartsWith("mud-drawer")))}");

await renderer.Dispatcher.InvokeAsync(() => navigation.NavigateTo("/"));
Assert.DoesNotContain("mud-drawer-open-responsive-md-left",
await renderer.Dispatcher.InvokeAsync(() => renderer.CssClasses(id).ToArray()));
}

Assert.Empty(renderer.Errors);
}

[Fact]
public async Task ClassSearch_FiltersResultsThroughMudInput()
{
Expand Down Expand Up @@ -161,6 +188,42 @@ await renderer.Dispatcher.InvokeAsync(async () =>
Assert.Empty(renderer.Errors);
}

[Fact]
public async Task ClassPage_RendersNavigationForSectionsAndOverloadedMembers()
{
await using var services = CreateServices();
var catalog = services.GetRequiredService<ClassDocumentationCatalog>();
var manager = services.GetRequiredService<VersionManagerService>();
var navigation = services.GetRequiredService<NavigationManager>();
navigation.NavigateTo("/en/26.1/Classes/Node?source=search");
var node = new ClassDocumentationEntry
{
Name = "Node",
Methods =
[
new ClassDocumentationCallable { Name = "add_child" },
new ClassDocumentationCallable { Name = "add_child" }
]
};
catalog.Publish(new ClassDocumentationSnapshot(manager.LatestStableVersion, "1234567890abcdef",
DateTimeOffset.UtcNow, new Dictionary<string, ClassDocumentationEntry> { ["Node"] = node }));

await using var renderer = new TestRenderer(services);
int id = await renderer.Dispatcher.InvokeAsync(() => renderer.RenderAsync(typeof(ClassDocViewer),
ParameterView.FromDictionary(new Dictionary<string, object?>
{ ["VersionSlug"] = "26.1", ["ClassName"] = "Node" })));

string rendered = await renderer.Dispatcher.InvokeAsync(() => renderer.Text(id));
string[] hrefs = await renderer.Dispatcher.InvokeAsync(() => renderer.Hrefs(id).ToArray());
Assert.Contains("desktop-toc", await renderer.Dispatcher.InvokeAsync(() => renderer.CssClasses(id).ToArray()));
Assert.Contains("mobile-toc", await renderer.Dispatcher.InvokeAsync(() => renderer.CssClasses(id).ToArray()));
Assert.Contains("<h3 id=\"method-add-child-2\">", rendered);
Assert.Contains("/en/26.1/Classes/Node?source=search#methods", hrefs);
Assert.Contains("/en/26.1/Classes/Node?source=search#method-add-child", hrefs);
Assert.Contains("/en/26.1/Classes/Node?source=search#method-add-child-2", hrefs);
Assert.Empty(renderer.Errors);
}

[Theory]
[InlineData(null)]
[InlineData("Node")]
Expand Down Expand Up @@ -411,11 +474,19 @@ protected override void NavigateToCore(string uri, bool forceLoad)
}
}

private sealed class TestJsRuntime : IJSRuntime
private sealed class TestJsRuntime : IJSRuntime, IJSObjectReference
{
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object?[]? args) => ValueTask.FromResult(default(TValue)!);
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object?[]? args)
{
object? result = typeof(TValue) == typeof(IJSObjectReference) ? this
: typeof(TValue) == typeof(MudBlazor.Breakpoint) ? MudBlazor.Breakpoint.Lg
: typeof(TValue) == typeof(BrowserWindowSize) ? new BrowserWindowSize { Width = 1280, Height = 900 }
: default(TValue);
return ValueTask.FromResult((TValue)result!);
}
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object?[]? args)
=> InvokeAsync<TValue>(identifier, args);
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}

private sealed class TestEnvironment(string root) : IWebHostEnvironment
Expand Down
57 changes: 56 additions & 1 deletion Redot-Documentation-Tests/ClassDocumentationRendererTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Text.RegularExpressions;
using HtmlAgilityPack;
using Redot_Documentation.ClassDocumentation;
using Redot_Documentation.Components.Layout;
using Redot_Documentation.Versioning;

namespace Redot_Documentation_Tests;
Expand Down Expand Up @@ -39,7 +41,60 @@ public void RenderPage_RendersSignaturesStatusesAndStableAnchors()
Assert.Contains("id=\"method-add-child\"", html);
Assert.Contains("href=\"/en/latest/Classes/Object\"", html);
Assert.Contains("class-status-experimental", html);
Assert.Contains("<code>Node</code></a> child", html);
Assert.Contains("<span class=\"token class-name\">Node</span></a> <span class=\"token variable\">child</span>", html);
}

[Fact]
public void RenderPage_HighlightsSignaturesAndCollectsMatchingMemberHeadings()
{
var renderer = new ClassDocumentationRenderer();
ClassDocumentationSnapshot snapshot = CreateSnapshot();
ClassDocumentationEntry node = snapshot.Classes["Node"] with
{
Description = "A useful class.",
Members = [new ClassDocumentationMember { Name = "enabled", Type = "bool", Default = "true" }],
Methods =
[
new ClassDocumentationCallable
{
Name = "add_child", ReturnType = "void", Qualifiers = "virtual const",
Parameters = [new ClassDocumentationParameter("child", "Node", "Node(1, 2)", null, false)]
},
new ClassDocumentationCallable
{
Name = "add_child", ReturnType = "void",
Parameters = [new ClassDocumentationParameter("unsafe", "String", "\"<script>\"", null, false)]
}
],
Constants = [new ClassDocumentationConstant { Name = "MAX_COUNT", Value = "7" }]
};
var headings = new List<DocumentHeading>();

string html = renderer.RenderPage(node, snapshot, headings);
var document = new HtmlDocument();
document.LoadHtml(html);

Assert.Equal(
[
new(2, "description", "Description"),
new(2, "properties", "Properties"),
new(3, "member-enabled", "enabled"),
new(2, "methods", "Methods"),
new(3, "method-add-child", "add_child"),
new(3, "method-add-child-2", "add_child (overload 2)"),
new(2, "constants", "Constants"),
new(3, "constant-max-count", "MAX_COUNT")
], headings);
Assert.Equal(headings.Select(heading => heading.Id),
document.DocumentNode.SelectNodes("//h2[@id]|//h3[@id]")!.Select(node => node.GetAttributeValue("id", "")));
Assert.Contains("<span class=\"token boolean\">true</span>", html);
Assert.Contains("<span class=\"token keyword\">virtual const</span>", html);
Assert.Contains("<span class=\"token function\">Node</span>", html);
Assert.Contains("<span class=\"token number\">7</span>", html);
Assert.Contains("href=\"/en/latest/Classes/Node\"><span class=\"token class-name\">Node</span></a>", html);
Assert.Contains("&lt;script&gt;", html);
Assert.DoesNotContain("<script>", html);
Assert.DoesNotContain("<code><a", html);
}

[Fact]
Expand Down
Loading
Loading