mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-13 23:07:21 +00:00
196 lines
7.5 KiB
C#
196 lines
7.5 KiB
C#
using System.Collections.Immutable;
|
|
|
|
using AIStudio.Tests.Models.Generation;
|
|
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.Text;
|
|
|
|
using SourceGeneratedMappings;
|
|
|
|
namespace AIStudio.Tests.Icons;
|
|
|
|
/// <summary>
|
|
/// Checks that the code points of the Lucide font become icon strings which MudBlazor shows as font icons.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The generator runs against code points written for the purpose. Whether it also ran while the app was
|
|
/// built, against the file the app ships, is asked in LucideIconsTests.
|
|
/// </remarks>
|
|
[TestFixture]
|
|
public sealed class LucideIconGeneratorTests
|
|
{
|
|
private const string CODEPOINTS_PATH = "/repository/app/SourceGeneratedMappings/Lucide/codepoints.json";
|
|
|
|
/// <summary>
|
|
/// Three names in the wrong order, two of which share their glyph, as aliases do in the real file.
|
|
/// </summary>
|
|
private const string THREE_ICONS =
|
|
"""
|
|
{
|
|
"trash-2": 57742,
|
|
"house": 57589,
|
|
"home": 57589
|
|
}
|
|
""";
|
|
|
|
[Test]
|
|
public void EveryNameBecomesAConstantHoldingItsGlyph()
|
|
{
|
|
Generate(THREE_ICONS, CODEPOINTS_PATH, out var updated, out _);
|
|
|
|
using (Assert.EnterMultipleScope())
|
|
{
|
|
Assert.That(ConstantOf(updated, "TRASH_2"), Is.EqualTo(FontIcon(0xE18E)));
|
|
Assert.That(ConstantOf(updated, "HOUSE"), Is.EqualTo(FontIcon(0xE0F5)));
|
|
Assert.That(ConstantOf(updated, "HOME"), Is.EqualTo(FontIcon(0xE0F5)), "An alias is a name of its own.");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void AnAliasNamesTheIconsItSharesItsGlyphWith()
|
|
{
|
|
var generated = Generate(THREE_ICONS, CODEPOINTS_PATH, out _, out _);
|
|
|
|
using (Assert.EnterMultipleScope())
|
|
{
|
|
Assert.That(generated["LucideIcons.g.cs"], Does.Contain("The Lucide icon \"home\", the same glyph as \"house\"."));
|
|
Assert.That(generated["LucideIcons.g.cs"], Does.Contain("The Lucide icon \"trash-2\"."));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void TheRegistryKnowsEveryNameAliasesIncluded()
|
|
{
|
|
var generated = Generate(THREE_ICONS, CODEPOINTS_PATH, out _, out _);
|
|
|
|
using (Assert.EnterMultipleScope())
|
|
{
|
|
Assert.That(generated["LucideIconRegistry.g.cs"], Does.Contain("[\"trash-2\"] = LucideIcons.TRASH_2,"));
|
|
Assert.That(generated["LucideIconRegistry.g.cs"], Does.Contain("[\"house\"] = LucideIcons.HOUSE,"));
|
|
Assert.That(generated["LucideIconRegistry.g.cs"], Does.Contain("[\"home\"] = LucideIcons.HOME,"));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void TheIconsAreWrittenInTheSameOrderEveryTime()
|
|
{
|
|
//
|
|
// The order of the file is not something the generated code may depend on: the same icons have
|
|
// to produce the same bytes, or an update of the font shows up as a reshuffled class.
|
|
//
|
|
var icons = Generate(THREE_ICONS, CODEPOINTS_PATH, out _, out _)["LucideIcons.g.cs"];
|
|
|
|
using (Assert.EnterMultipleScope())
|
|
{
|
|
Assert.That(icons.IndexOf(" HOME = ", StringComparison.Ordinal), Is.LessThan(icons.IndexOf(" HOUSE = ", StringComparison.Ordinal)));
|
|
Assert.That(icons.IndexOf(" HOUSE = ", StringComparison.Ordinal), Is.LessThan(icons.IndexOf(" TRASH_2 = ", StringComparison.Ordinal)));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void WhatIsGeneratedCompiles()
|
|
{
|
|
Generate(THREE_ICONS, CODEPOINTS_PATH, out var updated, out _);
|
|
|
|
Assert.That(CompilationHarness.ErrorsOf(updated), Is.Empty);
|
|
}
|
|
|
|
[Test]
|
|
public void WithoutTheFileNothingIsGeneratedAndTheReasonIsReported()
|
|
{
|
|
var generated = Generate(null, CODEPOINTS_PATH, out _, out var diagnostics);
|
|
|
|
using (Assert.EnterMultipleScope())
|
|
{
|
|
Assert.That(generated, Is.Empty);
|
|
Assert.That(diagnostics.Select(diagnostic => diagnostic.Id), Is.EqualTo(new[] { "LUC001" }));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void AFileOfTheSameNameInAnotherFolderIsNotTaken()
|
|
{
|
|
var generated = Generate(THREE_ICONS, "/repository/app/SomethingElse/codepoints.json", out _, out var diagnostics);
|
|
|
|
using (Assert.EnterMultipleScope())
|
|
{
|
|
Assert.That(generated, Is.Empty);
|
|
Assert.That(diagnostics.Select(diagnostic => diagnostic.Id), Is.EqualTo(new[] { "LUC001" }));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void AFileWithoutIconsIsReported()
|
|
{
|
|
var generated = Generate("{}", CODEPOINTS_PATH, out _, out var diagnostics);
|
|
|
|
using (Assert.EnterMultipleScope())
|
|
{
|
|
Assert.That(generated, Is.Empty);
|
|
Assert.That(diagnostics.Select(diagnostic => diagnostic.Id), Is.EqualTo(new[] { "LUC002" }));
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void AnEntryWhichCannotBeAnIconIsReportedRatherThanSkippedQuietly()
|
|
{
|
|
const string CODEPOINTS =
|
|
"""
|
|
{
|
|
"Not A Name": 57400,
|
|
"3d-box": 57401,
|
|
"plain-letter": 65,
|
|
"trash-2": 57742
|
|
}
|
|
""";
|
|
|
|
var generated = Generate(CODEPOINTS, CODEPOINTS_PATH, out var updated, out var diagnostics);
|
|
var skipped = diagnostics.Where(diagnostic => diagnostic.Id is "LUC003").Select(diagnostic => diagnostic.GetMessage()).ToList();
|
|
|
|
using (Assert.EnterMultipleScope())
|
|
{
|
|
Assert.That(skipped, Has.Count.EqualTo(3));
|
|
Assert.That(skipped, Has.Some.Contains("'Not A Name'"));
|
|
Assert.That(skipped, Has.Some.Contains("'3d-box'"));
|
|
Assert.That(skipped, Has.Some.Contains("'plain-letter'").And.Contains("private use"));
|
|
Assert.That(ConstantOf(updated, "TRASH_2"), Is.EqualTo(FontIcon(0xE18E)), "The valid entries still count.");
|
|
Assert.That(generated["LucideIcons.g.cs"], Does.Not.Contain("PLAIN_LETTER"));
|
|
}
|
|
}
|
|
|
|
private static string FontIcon(int codepoint) => "lucide/" + char.ConvertFromUtf32(codepoint);
|
|
|
|
//
|
|
// Asked of the snippet's own assembly: the app assembly, which the snippet references, has a class of
|
|
// the same name, generated from the real file.
|
|
//
|
|
private static object? ConstantOf(Compilation compilation, string name) => compilation.Assembly
|
|
.GetTypeByMetadataName("AIStudio.Tools.LucideIcons")!
|
|
.GetMembers(name)
|
|
.OfType<IFieldSymbol>()
|
|
.Single()
|
|
.ConstantValue;
|
|
|
|
/// <summary>
|
|
/// Runs the generator, with the code points as the only additional file, or without any file at all.
|
|
/// </summary>
|
|
/// <returns>The generated sources by their hint names.</returns>
|
|
private static IReadOnlyDictionary<string, string> Generate(string? codepoints, string path, out Compilation updated, out ImmutableArray<Diagnostic> diagnostics)
|
|
{
|
|
var compilation = CompilationHarness.Compile("namespace Sample;\n\npublic sealed class NothingToDoWithIcons;");
|
|
AdditionalText[] additionalTexts = codepoints is null ? [] : [new InMemoryAdditionalText(path, codepoints)];
|
|
|
|
var driver = CSharpGeneratorDriver.Create([new LucideIconGenerator().AsSourceGenerator()], additionalTexts);
|
|
var afterwards = driver.RunGeneratorsAndUpdateCompilation(compilation, out updated, out diagnostics);
|
|
|
|
return afterwards.GetRunResult().Results.Single().GeneratedSources.ToDictionary(source => source.HintName, source => source.SourceText.ToString());
|
|
}
|
|
|
|
private sealed class InMemoryAdditionalText(string path, string content) : AdditionalText
|
|
{
|
|
public override string Path => path;
|
|
|
|
public override SourceText GetText(CancellationToken cancellationToken = default) => SourceText.From(content);
|
|
}
|
|
} |