Turned getter-like extension methods into extension properties (#1157)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-04 13:00:56 +01:00
1 parent 0375318b1b
commit f649febf88
286 files changed
+1379 -1109

No files matched your search

@@ -0,0 +1,192 @@
using AIStudio.Tests.Models.Generation;
using SourceCodeRules.StyleAnalyzers;
using SourceCodeRules.StyleCodeFixes;
namespace AIStudio.Tests.Analyzers;
/// <summary>
/// Checks that methods and properties which only return a switch expression start it on the line of their arrow,
/// and that the code fix of MWAIS0006 keeps everything else of the member.
/// </summary>
/// <remarks>
/// Extension blocks turn many such methods into properties, so the rule has to know properties as well: a
/// getter which does nothing but return a switch expression reads like the method it replaced.
/// </remarks>
[TestFixture]
public sealed class SwitchExpressionMethodAnalyzerTests
{
private const string SWITCH = "this.Kind switch { Kind.CALL => \"Call\", _ => \"Meeting\" }";
[TestCase("public string Label() { return " + SWITCH + "; }", "Method", TestName = "A method which returns a switch expression from a block")]
[TestCase("public string Label() =>\n" + SWITCH + ";", "Method", TestName = "A method whose switch expression starts on the next line")]
[TestCase("public string Label =>\n" + SWITCH + ";", "Property", TestName = "A property whose switch expression starts on the next line")]
[TestCase("public string Label { get { return " + SWITCH + "; } }", "Property", TestName = "A getter which returns a switch expression from a block")]
[TestCase("public string Label { get => " + SWITCH + "; }", "Property", TestName = "A getter with a switch expression as its body")]
public async Task AMemberOfAClassIsRefused(string member, string kind)
{
var reported = await CompilationHarness.AnalyzeAsync(ClassWith(member), new SwitchExpressionMethodAnalyzer());
using (Assert.EnterMultipleScope())
{
Assert.That(reported, Has.Count.EqualTo(1));
Assert.That(reported.FirstOrDefault()?.Id, Is.EqualTo("MWAIS0006"));
Assert.That(reported.FirstOrDefault()?.GetMessage(), Does.StartWith($"{kind} with a switch expression"));
}
}
[TestCase("public string Label =>\nkind switch { Kind.CALL => \"Call\", _ => \"Meeting\" };", TestName = "An extension property whose switch expression starts on the next line")]
[TestCase("public string Label { get { return kind switch { Kind.CALL => \"Call\", _ => \"Meeting\" }; } }", TestName = "The getter of an extension property which returns a switch expression from a block")]
[TestCase("public string Label() { return kind switch { Kind.CALL => \"Call\", _ => \"Meeting\" }; }", TestName = "An extension method which returns a switch expression from a block")]
public async Task AMemberOfAnExtensionBlockIsRefused(string member)
{
var reported = await CompilationHarness.AnalyzeAsync(ExtensionBlockWith(member), new SwitchExpressionMethodAnalyzer());
using (Assert.EnterMultipleScope())
{
Assert.That(reported, Has.Count.EqualTo(1));
Assert.That(reported.FirstOrDefault()?.Id, Is.EqualTo("MWAIS0006"));
}
}
[TestCase("public string Label() => " + SWITCH + ";", TestName = "A method with the switch expression on the line of its arrow")]
[TestCase("public string Label => " + SWITCH + ";", TestName = "A property with the switch expression on the line of its arrow")]
[TestCase("public string Label { get { return " + SWITCH + "; } set { } }", TestName = "A property with a getter and a setter")]
[TestCase("public string Label { get { var kind = this.Kind; return kind switch { Kind.CALL => \"Call\", _ => \"Meeting\" }; } }", TestName = "A getter which does more than return a switch expression")]
[TestCase("public string Label() { return \"Meeting\"; }", TestName = "A method which returns no switch expression")]
public async Task AnythingElseIsLeftAlone(string member)
{
var reported = await CompilationHarness.AnalyzeAsync(ClassWith(member), new SwitchExpressionMethodAnalyzer());
Assert.That(reported, Is.Empty);
}
[Test]
public async Task TheCodeFixKeepsTheAttributesTypeParametersAndConstraintsOfAMethod()
{
var source = ClassWith(
"""
/// <summary>Names the hash of a value.</summary>
[System.Diagnostics.Contracts.Pure]
public static string Label<T>(T value) where T : struct
{
return value.GetHashCode() switch
{
0 => "Zero",
_ => "Other",
};
}
""");
var expected = ClassWith(
"""
/// <summary>Names the hash of a value.</summary>
[System.Diagnostics.Contracts.Pure]
public static string Label<T>(T value) where T : struct => value.GetHashCode() switch
{
0 => "Zero",
_ => "Other",
};
""");
var fixedSource = await CompilationHarness.FixAsync(source, new SwitchExpressionMethodAnalyzer(), new SwitchExpressionMethodCodeFixProvider());
Assert.That(fixedSource.ReplaceLineEndings("\n"), Is.EqualTo(expected.ReplaceLineEndings("\n")));
}
[Test]
public async Task TheCodeFixTurnsAGetterIntoAnExpressionBody()
{
var source = ClassWith(
"""
public string Label
{
get
{
return this.Kind switch
{
Kind.CALL => "Call",
_ => "Meeting",
};
}
}
""");
var expected = ClassWith(
"""
public string Label => this.Kind switch
{
Kind.CALL => "Call",
_ => "Meeting",
};
""");
var fixedSource = await CompilationHarness.FixAsync(source, new SwitchExpressionMethodAnalyzer(), new SwitchExpressionMethodCodeFixProvider());
Assert.That(fixedSource.ReplaceLineEndings("\n"), Is.EqualTo(expected.ReplaceLineEndings("\n")));
}
[Test]
public async Task TheCodeFixMovesTheSwitchOfAnExtensionPropertyOntoTheLineOfItsArrow()
{
var source = ExtensionBlockWith(
"""
public string Label =>
kind switch
{
Kind.CALL => "Call",
_ => "Meeting",
};
""");
var expected = ExtensionBlockWith(
"""
public string Label => kind switch
{
Kind.CALL => "Call",
_ => "Meeting",
};
""");
var fixedSource = await CompilationHarness.FixAsync(source, new SwitchExpressionMethodAnalyzer(), new SwitchExpressionMethodCodeFixProvider());
Assert.That(fixedSource.ReplaceLineEndings("\n"), Is.EqualTo(expected.ReplaceLineEndings("\n")));
}
private static string ClassWith(string members) =>
$$"""
namespace Sample;
public enum Kind
{
CALL,
MEETING,
}
public sealed class Meeting
{
public Kind Kind { get; set; } = Kind.MEETING;
{{members}}
}
""";
private static string ExtensionBlockWith(string members) =>
$$"""
namespace Sample;
public enum Kind
{
CALL,
MEETING,
}
public static class KindExtensions
{
extension(Kind kind)
{
{{members}}
}
}
""";
}
+1 -1
View File
@@ -25,7 +25,7 @@ public sealed class SendToReceiverTests
private static readonly Assembly APP = typeof(AssistantLaunchService).Assembly;
private static IEnumerable<AIStudio.Tools.Components> Destinations() => Enum.GetValues<AIStudio.Tools.Components>()
.Where(component => component.AllowSendTo())
.Where(component => component.AllowSendTo)
.Where(component => component is not AIStudio.Tools.Components.CHAT);
[TestCaseSource(nameof(Destinations))]
@@ -1,6 +1,9 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
namespace AIStudio.Tests.Models.Generation;
@@ -70,6 +73,53 @@ public static class CompilationHarness
return reported;
}
/// <summary>
/// Runs one analyzer over a snippet and applies its code fix to the first diagnostic it reports.
/// </summary>
/// <remarks>
/// The code fix runs in a workspace of its own, as it does in the IDE. The fixed source still has to
/// compile, since a code fix which breaks the code is worse than none.
/// </remarks>
/// <param name="source">The C# source to fix.</param>
/// <param name="analyzer">The analyzer which reports what to fix.</param>
/// <param name="codeFix">The code fix to apply.</param>
/// <returns>The fixed source.</returns>
public static async Task<string> FixAsync(string source, DiagnosticAnalyzer analyzer, CodeFixProvider codeFix)
{
using var workspace = new AdhocWorkspace();
var projectInfo = ProjectInfo.Create(
ProjectId.CreateNewId(),
VersionStamp.Default,
"SnippetUnderTest",
"SnippetUnderTest",
LanguageNames.CSharp,
compilationOptions: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, nullableContextOptions: NullableContextOptions.Enable),
parseOptions: new CSharpParseOptions(LanguageVersion.Latest),
metadataReferences: REFERENCES.Value);
var project = workspace.AddProject(projectInfo);
var document = workspace.AddDocument(project.Id, "Snippet.cs", SourceText.From(source));
var compilation = await document.Project.GetCompilationAsync();
Assert.That(ErrorsOf(compilation!), Is.Empty, "The snippet has to compile, or the code fix is being asked about code which does not exist.");
var reported = await compilation!.WithAnalyzers([analyzer]).GetAnalyzerDiagnosticsAsync();
Assert.That(reported, Is.Not.Empty, "The analyzer has to report something, or there is nothing for the code fix to do.");
var actions = new List<CodeAction>();
var context = new CodeFixContext(document, reported[0], (action, _) => actions.Add(action), CancellationToken.None);
await codeFix.RegisterCodeFixesAsync(context);
Assert.That(actions, Has.Count.EqualTo(1), "The code fix has to offer exactly one action.");
var operations = await actions[0].GetOperationsAsync(CancellationToken.None);
var changedSolution = operations.OfType<ApplyChangesOperation>().Single().ChangedSolution;
var fixedText = await changedSolution.GetDocument(document.Id)!.GetTextAsync();
var fixedSource = fixedText.ToString();
Assert.That(ErrorsOf(Compile(fixedSource)), Is.Empty, "The fixed source has to compile.");
return fixedSource;
}
private static MetadataReference[] GatherReferences()
{
//
+3 -3
View File
@@ -19,7 +19,7 @@ public sealed class ImageSizeLimitTests
{
using (Assert.EnterMultipleScope())
{
Assert.That(LLMProviders.ANTHROPIC.GetMaxImageSizeBytes(), Is.EqualTo(3_932_160));
Assert.That(LLMProviders.ANTHROPIC.MaxImageSizeBytes, Is.EqualTo(3_932_160));
Assert.That(IImageSourceExtensions.MaxImageSizeBytesFor(ProviderOf(LLMProviders.ANTHROPIC)), Is.EqualTo(3_932_160));
}
}
@@ -29,7 +29,7 @@ public sealed class ImageSizeLimitTests
{
using (Assert.EnterMultipleScope())
{
Assert.That(LLMProviders.OPEN_AI.GetMaxImageSizeBytes(), Is.Null, "Nobody stated a limit, and no number stands in for that.");
Assert.That(LLMProviders.OPEN_AI.MaxImageSizeBytes, Is.Null, "Nobody stated a limit, and no number stands in for that.");
Assert.That(IImageSourceExtensions.MaxImageSizeBytesFor(ProviderOf(LLMProviders.OPEN_AI)), Is.EqualTo(IImageSourceExtensions.MAX_IMAGE_SIZE_BYTES));
}
}
@@ -39,7 +39,7 @@ public sealed class ImageSizeLimitTests
{
using (Assert.EnterMultipleScope())
{
Assert.That(Host.LLMMAN.GetMaxImageSizeBytes(), Is.EqualTo(1_048_576));
Assert.That(Host.LLMMAN.MaxImageSizeBytes, Is.EqualTo(1_048_576));
Assert.That(IImageSourceExtensions.MaxImageSizeBytesFor(ProviderOf(LLMProviders.SELF_HOSTED, Host.LLMMAN)), Is.EqualTo(1_048_576));
Assert.That(IImageSourceExtensions.MaxImageSizeBytesFor(ProviderOf(LLMProviders.SELF_HOSTED, Host.OLLAMA)), Is.EqualTo(IImageSourceExtensions.MAX_IMAGE_SIZE_BYTES), "Ollama states no limit we know of.");
}
@@ -33,7 +33,7 @@ public sealed class SystemPromptRoleTests
{
var profile = LLMProviders.OPEN_AI.GetModelProfile(new Model(modelId, null));
Assert.That(profile.SystemPromptRole.ToOpenAIRole(), Is.EqualTo(role));
Assert.That(profile.SystemPromptRole.OpenAIRole, Is.EqualTo(role));
}
[Test]
@@ -43,6 +43,6 @@ public sealed class SystemPromptRoleTests
// A model the rules do not know is almost always a new one, and the older generations which
// still want "system" say so in their families.
//
Assert.That(SystemPromptRole.UNKNOWN.ToOpenAIRole(), Is.EqualTo("developer"));
Assert.That(SystemPromptRole.UNKNOWN.OpenAIRole, Is.EqualTo("developer"));
}
}
@@ -21,7 +21,7 @@ public sealed class HostExtensionsTests
[TestCaseSource(nameof(ALL_HOSTS))]
public void TheDialogsOfferToLoadModelsExactlyWhereTheEngineCanListThem(Host host)
{
Assert.That(LLMProviders.SELF_HOSTED.CanLoadModels(host, null), Is.EqualTo(host.IsModelListSupported()));
Assert.That(LLMProviders.SELF_HOSTED.CanLoadModels(host, null), Is.EqualTo(host.IsModelListSupported));
}
[Test]
@@ -30,7 +30,7 @@ public sealed class HostExtensionsTests
//
// There is no fixed chat model to offer instead, so such an engine would offer an empty list:
//
var unlisted = ALL_HOSTS.Where(host => host is not Host.NONE && host.IsChatSupported() && !host.IsModelListSupported());
var unlisted = ALL_HOSTS.Where(host => host is not Host.NONE && host.IsChatSupported && !host.IsModelListSupported);
Assert.That(unlisted, Is.Empty);
}
@@ -40,7 +40,7 @@ public sealed class HostExtensionsTests
//
// There is no fixed embedding model to offer instead, so such an engine would offer an empty list:
//
var unlisted = ALL_HOSTS.Where(host => host.IsEmbeddingSupported() && !host.IsModelListSupported());
var unlisted = ALL_HOSTS.Where(host => host.IsEmbeddingSupported && !host.IsModelListSupported);
Assert.That(unlisted, Is.Empty);
}
@@ -51,7 +51,7 @@ public sealed class HostExtensionsTests
// The provider offers the one model of whisper.cpp under a fixed name. Any other engine
// which transcribes but cannot list its models would offer an empty list:
//
var unlisted = ALL_HOSTS.Where(host => host.IsTranscriptionSupported() && !host.IsModelListSupported());
var unlisted = ALL_HOSTS.Where(host => host.IsTranscriptionSupported && !host.IsModelListSupported);
Assert.That(unlisted, Is.EqualTo(new[] { Host.WHISPER_CPP }));
}
@@ -60,9 +60,9 @@ public sealed class HostExtensionsTests
{
using (Assert.EnterMultipleScope())
{
Assert.That(Host.NONE.IsModelListSupported(), Is.False);
Assert.That(Host.NONE.IsEmbeddingSupported(), Is.False);
Assert.That(Host.NONE.IsTranscriptionSupported(), Is.False);
Assert.That(Host.NONE.IsModelListSupported, Is.False);
Assert.That(Host.NONE.IsEmbeddingSupported, Is.False);
Assert.That(Host.NONE.IsTranscriptionSupported, Is.False);
Assert.That(LLMProviders.SELF_HOSTED.CanLoadModels(Host.NONE, "a key"), Is.False);
}
}
+5 -5
View File
@@ -20,7 +20,7 @@ public sealed class EnumNamesTests
[TestCase(" Every_Message ")]
public void ANameCountsWhateverItsCase(string text)
{
Assert.That(EnumNames.TryParse<DataSourceRetrievalMode>(text, out var mode), Is.True);
Assert.That(DataSourceRetrievalMode.TryParseName(text, out var mode), Is.True);
Assert.That(mode, Is.EqualTo(DataSourceRetrievalMode.EVERY_MESSAGE));
}
@@ -29,7 +29,7 @@ public sealed class EnumNamesTests
[TestCase("0x1")]
public void ANumberDoesNotCountEvenWhenAMemberHasIt(string text)
{
Assert.That(EnumNames.TryParse<DataSourceRetrievalMode>(text, out _), Is.False, "Enum.TryParse would read 1 as EVERY_MESSAGE.");
Assert.That(DataSourceRetrievalMode.TryParseName(text, out _), Is.False, "Enum.TryParse would read 1 as EVERY_MESSAGE.");
}
[Test]
@@ -37,8 +37,8 @@ public sealed class EnumNamesTests
{
using (Assert.EnterMultipleScope())
{
Assert.That(EnumNames.TryParse<DataSourceRetrievalMode>("SEMANTIC_SEARCH, EVERY_MESSAGE", out _), Is.False, "Enum.TryParse would combine them to EVERY_MESSAGE.");
Assert.That(EnumNames.TryParse<ConfidenceLevel>("HIGH, MEDIUM", out _), Is.False, "Enum.TryParse would combine them to a confidence level above HIGH.");
Assert.That(DataSourceRetrievalMode.TryParseName("SEMANTIC_SEARCH, EVERY_MESSAGE", out _), Is.False, "Enum.TryParse would combine them to EVERY_MESSAGE.");
Assert.That(ConfidenceLevel.TryParseName("HIGH, MEDIUM", out _), Is.False, "Enum.TryParse would combine them to a confidence level above HIGH.");
}
}
@@ -48,7 +48,7 @@ public sealed class EnumNamesTests
[TestCase("EVERY MESSAGE")]
public void NoNameDoesNotCount(string? text)
{
Assert.That(EnumNames.TryParse<DataSourceRetrievalMode>(text, out var mode), Is.False);
Assert.That(DataSourceRetrievalMode.TryParseName(text, out var mode), Is.False);
Assert.That(mode, Is.EqualTo(default(DataSourceRetrievalMode)));
}
@@ -29,7 +29,7 @@ public sealed class TranscriptionOpusBitrateTests
foreach (var bitrate in Enum.GetValues<TranscriptionOpusBitrate>())
{
Assert.That(EXPECTED_BITS_PER_SECOND.ContainsKey(bitrate), Is.True, $"The selection offers {bitrate}, so this test has to state what that is worth in bits per second.");
Assert.That(bitrate.GetBitsPerSecond(), Is.EqualTo(EXPECTED_BITS_PER_SECOND[bitrate]), $"{bitrate} is what the user picked; anything else travels to the encoder behind their back.");
Assert.That(bitrate.BitsPerSecond, Is.EqualTo(EXPECTED_BITS_PER_SECOND[bitrate]), $"{bitrate} is what the user picked; anything else travels to the encoder behind their back.");
}
}
@@ -39,7 +39,7 @@ public sealed class TranscriptionOpusBitrateTests
var undeclared = (TranscriptionOpusBitrate)999;
Assert.That(Enum.IsDefined(undeclared), Is.False, "The point of this test is a value outside the enum; a declared one would prove nothing.");
Assert.That(undeclared.GetBitsPerSecond(), Is.EqualTo(128_000u), "Not knowing which quality was meant is no reason to pick the worst one available.");
Assert.That(undeclared.BitsPerSecond, Is.EqualTo(128_000u), "Not knowing which quality was meant is no reason to pick the worst one available.");
}
[Test]
@@ -24,7 +24,7 @@ public sealed class WebRootFilesTests
{
private static IEnumerable<string> ChangelogFiles() => Changelog.LOGS.Select(log => log.Filename);
private static IEnumerable<ERIVersion> SpecificationVersions() => Enum.GetValues<ERIVersion>().Where(version => version.WasSpecificationSelected());
private static IEnumerable<ERIVersion> SpecificationVersions() => Enum.GetValues<ERIVersion>().Where(version => version.WasSpecificationSelected);
private static string ReadAppPageSource([CallerFilePath] string sourceFilePath = "") => File.ReadAllText(Path.Combine(Path.GetDirectoryName(sourceFilePath)!, "..", "..", "..", "MindWork AI Studio", "App.razor"));
@@ -78,7 +78,7 @@ public sealed class WebRootFilesTests
var destinationPath = Path.Combine(Path.GetTempPath(), $"eri-specification-{Guid.NewGuid():N}.json");
try
{
Assert.That(await webRootFiles.CopyToAsync(version.SpecificationURL(), destinationPath), Is.True);
Assert.That(await webRootFiles.CopyToAsync(version.SpecificationURL, destinationPath), Is.True);
Assert.That(await File.ReadAllTextAsync(destinationPath), Is.EqualTo(await version.ReadSpecification(webRootFiles)));
}
finally
@@ -42,7 +42,7 @@ public sealed class AssistantLaunchServiceTests
using (Assert.EnterMultipleScope())
{
Assert.That(destinations, Is.Not.Empty);
Assert.That(destinations.All(destination => destination.AllowSendTo()), Is.True);
Assert.That(destinations.All(destination => destination.AllowSendTo), Is.True);
Assert.That(destinations, Does.Not.Contain(AIStudio.Tools.Components.DYNAMIC_ASSISTANT), "Assistant plugins cannot take content yet.");
Assert.That(destinations, Does.Not.Contain(AIStudio.Tools.Components.NONE));
}
@@ -58,7 +58,7 @@ public sealed class AssistantLaunchServiceTests
[Test]
public void ShorterNamesComeFirst()
{
var lengths = AssistantLaunchService.SelectDestinations(AIStudio.Tools.Components.CHAT, _ => true).Select(destination => destination.Name().Length).ToList();
var lengths = AssistantLaunchService.SelectDestinations(AIStudio.Tools.Components.CHAT, _ => true).Select(destination => destination.Name.Length).ToList();
Assert.That(lengths, Is.Ordered);
}
}
@@ -77,7 +77,7 @@ public sealed class CodeLanguageDetectorTests
{
using (Assert.EnterMultipleScope())
{
var isFormat = FileExportFormatExtensions.TryFromCodeFenceLanguage(fenceLanguage, out _);
var isFormat = FileExportFormat.TryFromCodeFenceLanguage(fenceLanguage, out _);
Assert.That(isFormat || SourceCodeLanguages.Aliases.Contains(fenceLanguage, StringComparer.OrdinalIgnoreCase), Is.True, "The export would save it as a text file.");
Assert.That(CodeHighlighters.TryGetRules(fenceLanguage, out _) || LIBRARY_LANGUAGES.Contains(fenceLanguage), Is.True, "Nobody highlights it.");
}
@@ -14,7 +14,7 @@ public sealed class CodeTokenKindExtensionsTests
public async Task EveryKindHasAClassWhichTheStyleSheetColors([Values] CodeTokenKind kind)
{
var styleSheet = await new WebRootFiles().ReadTextAsync("system/MudBlazor.Markdown/MudBlazor.Markdown.min.css");
var cssClass = kind.ToCssClass().Split(' ')[0];
var cssClass = kind.CssClass.Split(' ')[0];
Assert.That(styleSheet, Does.Contain($".{cssClass}"), "A library which renames its classes would leave this kind without color.");
}
+26 -26
View File
@@ -13,11 +13,11 @@ public sealed class FileExportFormatTests
{
using (Assert.EnterMultipleScope())
{
Assert.That(FileExportFormat.MICROSOFT_WORD.FollowsPageAnchors(), Is.False, "Word looks for a file whose name ends in the fragment, finds none, and refuses the link.");
Assert.That(FileExportFormat.OPEN_DOCUMENT_TEXT.FollowsPageAnchors(), Is.False, "LibreOffice does the same, verified on 2026-09-15 with an exported .odt.");
Assert.That(FileExportFormat.HTML.FollowsPageAnchors(), Is.True, "A browser opens the document on the page the fragment names.");
Assert.That(FileExportFormat.MARKDOWN.FollowsPageAnchors(), Is.True);
Assert.That(FileExportFormat.LATEX.FollowsPageAnchors(), Is.True);
Assert.That(FileExportFormat.MICROSOFT_WORD.FollowsPageAnchors, Is.False, "Word looks for a file whose name ends in the fragment, finds none, and refuses the link.");
Assert.That(FileExportFormat.OPEN_DOCUMENT_TEXT.FollowsPageAnchors, Is.False, "LibreOffice does the same, verified on 2026-09-15 with an exported .odt.");
Assert.That(FileExportFormat.HTML.FollowsPageAnchors, Is.True, "A browser opens the document on the page the fragment names.");
Assert.That(FileExportFormat.MARKDOWN.FollowsPageAnchors, Is.True);
Assert.That(FileExportFormat.LATEX.FollowsPageAnchors, Is.True);
}
}
@@ -27,7 +27,7 @@ public sealed class FileExportFormatTests
// Whoever adds a format decides what its reader can follow, rather than inheriting an
// assumption. This fails for a format which nobody thought about, because the list below
// has to name it:
Assert.That(FileExportFormatExtensions.ANSWER_FORMATS, Is.EquivalentTo(new[]
Assert.That(FileExportFormat.AnswerFormats, Is.EquivalentTo(new[]
{
FileExportFormat.MICROSOFT_WORD,
FileExportFormat.OPEN_DOCUMENT_TEXT,
@@ -59,10 +59,10 @@ public sealed class FileExportFormatTests
{
using (Assert.EnterMultipleScope())
{
Assert.That(FileExportFormat.HTML.NeedsPageTitle(), Is.True, "Without one, the browser tab shows the random name of the temporary file Pandoc read.");
Assert.That(FileExportFormat.MICROSOFT_WORD.NeedsPageTitle(), Is.False, "Word would keep it as a document property nobody asked for.");
Assert.That(FileExportFormat.OPEN_DOCUMENT_TEXT.NeedsPageTitle(), Is.False, "The same goes for an OpenDocument file.");
Assert.That(FileExportFormat.LATEX.NeedsPageTitle(), Is.False);
Assert.That(FileExportFormat.HTML.NeedsPageTitle, Is.True, "Without one, the browser tab shows the random name of the temporary file Pandoc read.");
Assert.That(FileExportFormat.MICROSOFT_WORD.NeedsPageTitle, Is.False, "Word would keep it as a document property nobody asked for.");
Assert.That(FileExportFormat.OPEN_DOCUMENT_TEXT.NeedsPageTitle, Is.False, "The same goes for an OpenDocument file.");
Assert.That(FileExportFormat.LATEX.NeedsPageTitle, Is.False);
}
}
@@ -81,9 +81,9 @@ public sealed class FileExportFormatTests
{
using (Assert.EnterMultipleScope())
{
Assert.That(FileExportFormatExtensions.TryFromCodeFenceLanguage(language, out var format), Is.True);
Assert.That(FileExportFormat.TryFromCodeFenceLanguage(language, out var format), Is.True);
Assert.That(format, Is.EqualTo(expectedFormat));
Assert.That(format.IsPlainText(), Is.True, "A code block holds text, so the export writes it as it is.");
Assert.That(format.IsPlainText, Is.True, "A code block holds text, so the export writes it as it is.");
}
}
@@ -92,16 +92,16 @@ public sealed class FileExportFormatTests
{
using (Assert.EnterMultipleScope())
{
Assert.That(FileExportFormat.MICROSOFT_WORD.IsPlainText(), Is.False, "A Word file is an archive, and writing text into one breaks it.");
Assert.That(FileExportFormat.OPEN_DOCUMENT_TEXT.IsPlainText(), Is.False);
Assert.That(FileExportFormat.NONE.IsPlainText(), Is.False, "No format means no file.");
Assert.That(FileExportFormat.UNKNOWN.IsPlainText(), Is.False);
Assert.That(FileExportFormat.HTML.IsPlainText(), Is.True, "A page the model wrote is a finished file, even though an entire answer needs Pandoc to become one.");
Assert.That(FileExportFormat.LATEX.IsPlainText(), Is.True);
Assert.That(FileExportFormat.MARKDOWN.IsPlainText(), Is.True);
Assert.That(FileExportFormat.CSV.IsPlainText(), Is.True);
Assert.That(FileExportFormat.TSV.IsPlainText(), Is.True);
Assert.That(FileExportFormat.SOURCE_CODE.IsPlainText(), Is.True, "Code is text, whatever its language.");
Assert.That(FileExportFormat.MICROSOFT_WORD.IsPlainText, Is.False, "A Word file is an archive, and writing text into one breaks it.");
Assert.That(FileExportFormat.OPEN_DOCUMENT_TEXT.IsPlainText, Is.False);
Assert.That(FileExportFormat.NONE.IsPlainText, Is.False, "No format means no file.");
Assert.That(FileExportFormat.UNKNOWN.IsPlainText, Is.False);
Assert.That(FileExportFormat.HTML.IsPlainText, Is.True, "A page the model wrote is a finished file, even though an entire answer needs Pandoc to become one.");
Assert.That(FileExportFormat.LATEX.IsPlainText, Is.True);
Assert.That(FileExportFormat.MARKDOWN.IsPlainText, Is.True);
Assert.That(FileExportFormat.CSV.IsPlainText, Is.True);
Assert.That(FileExportFormat.TSV.IsPlainText, Is.True);
Assert.That(FileExportFormat.SOURCE_CODE.IsPlainText, Is.True, "Code is text, whatever its language.");
}
}
@@ -110,9 +110,9 @@ public sealed class FileExportFormatTests
{
using (Assert.EnterMultipleScope())
{
Assert.That(FileExportFormat.SOURCE_CODE.ToFileExtension(), Is.Empty, "Python and Rust share the format, but not the extension.");
Assert.That(FileExportFormat.SOURCE_CODE.ToFileTypeFilter(), Is.Null, "Without the language, the save dialog would offer the wrong files.");
Assert.That(FileExportFormatExtensions.ANSWER_FORMATS, Does.Not.Contain(FileExportFormat.SOURCE_CODE), "Code is one block out of an answer, never the answer itself.");
Assert.That(FileExportFormat.SOURCE_CODE.FileExtension, Is.Empty, "Python and Rust share the format, but not the extension.");
Assert.That(FileExportFormat.SOURCE_CODE.FileTypeFilter, Is.Null, "Without the language, the save dialog would offer the wrong files.");
Assert.That(FileExportFormat.AnswerFormats, Does.Not.Contain(FileExportFormat.SOURCE_CODE), "Code is one block out of an answer, never the answer itself.");
}
}
@@ -124,7 +124,7 @@ public sealed class FileExportFormatTests
{
using (Assert.EnterMultipleScope())
{
Assert.That(FileExportFormatExtensions.TryFromCodeFenceLanguage(language, out var format), Is.False);
Assert.That(FileExportFormat.TryFromCodeFenceLanguage(language, out var format), Is.False);
Assert.That(format, Is.EqualTo(FileExportFormat.NONE));
}
}
@@ -63,8 +63,8 @@ public sealed class MailboxProviderTemplatesTests
[Test]
public void EveryUsernameFormatHasItsOwnDescription()
{
var descriptions = Enum.GetValues<MailboxUsernameFormat>().Select(format => format.GetDescription()).ToList();
var fallback = ((MailboxUsernameFormat)int.MaxValue).GetDescription();
var descriptions = Enum.GetValues<MailboxUsernameFormat>().Select(format => format.Description).ToList();
var fallback = ((MailboxUsernameFormat)int.MaxValue).Description;
using (Assert.EnterMultipleScope())
{
+4 -4
View File
@@ -162,10 +162,10 @@ public sealed class PassageProvenanceTests
{
using (Assert.EnterMultipleScope())
{
Assert.That(Passage(HANDBOOK_URL, "text").GetProvenanceKind(), Is.EqualTo(ProvenanceKind.DATA_SOURCE_PASSAGE));
Assert.That((Passage(HANDBOOK_URL, "text") with { Type = RetrievalContentType.TEXT_TRANSCRIPT }).GetProvenanceKind(), Is.EqualTo(ProvenanceKind.DATA_SOURCE_PASSAGE));
Assert.That((Passage(HANDBOOK_URL, "text") with { Type = RetrievalContentType.TEXT_EMAIL }).GetProvenanceKind(), Is.EqualTo(ProvenanceKind.MAIL), "A mail is a mail, whether a mailbox data source or a tool handed it over.");
Assert.That((Passage(HANDBOOK_URL, "text") with { Type = RetrievalContentType.TEXT_WEBSITE }).GetProvenanceKind(), Is.EqualTo(ProvenanceKind.WEB_PAGE));
Assert.That(Passage(HANDBOOK_URL, "text").ProvenanceKind, Is.EqualTo(ProvenanceKind.DATA_SOURCE_PASSAGE));
Assert.That((Passage(HANDBOOK_URL, "text") with { Type = RetrievalContentType.TEXT_TRANSCRIPT }).ProvenanceKind, Is.EqualTo(ProvenanceKind.DATA_SOURCE_PASSAGE));
Assert.That((Passage(HANDBOOK_URL, "text") with { Type = RetrievalContentType.TEXT_EMAIL }).ProvenanceKind, Is.EqualTo(ProvenanceKind.MAIL), "A mail is a mail, whether a mailbox data source or a tool handed it over.");
Assert.That((Passage(HANDBOOK_URL, "text") with { Type = RetrievalContentType.TEXT_WEBSITE }).ProvenanceKind, Is.EqualTo(ProvenanceKind.WEB_PAGE));
}
}
@@ -15,9 +15,9 @@ public sealed class LayoutPurposeTests
{
foreach (var purpose in Enum.GetValues<LayoutPurpose>())
{
Assert.That(purpose.Label(), Is.Not.Empty, $"The kind of slide {purpose} has no label.");
Assert.That(purpose.Question(), Does.EndWith("?"), $"The kind of slide {purpose} has no question.");
Assert.That(purpose.PromptDescription(), Is.Not.Empty.And.Not.EndWith("."), $"The kind of slide {purpose} has no description, or one which ends where the rule for the model goes on.");
Assert.That(purpose.Label, Is.Not.Empty, $"The kind of slide {purpose} has no label.");
Assert.That(purpose.Question, Does.EndWith("?"), $"The kind of slide {purpose} has no question.");
Assert.That(purpose.PromptDescription, Is.Not.Empty.And.Not.EndWith("."), $"The kind of slide {purpose} has no description, or one which ends where the rule for the model goes on.");
}
}
}
@@ -28,7 +28,7 @@ public sealed class LayoutPurposeTests
[Test]
public void EveryKindOfSlideHasALabelOfItsOwn()
{
var labels = Enum.GetValues<LayoutPurpose>().Select(purpose => purpose.Label()).ToList();
var labels = Enum.GetValues<LayoutPurpose>().Select(purpose => purpose.Label).ToList();
Assert.That(labels, Is.Unique);
}
@@ -42,7 +42,7 @@ public sealed class LayoutPurposeTests
[Test]
public void TheKindsOfSlidesFollowTheOrderOfTheAreas()
{
var areas = Enum.GetValues<LayoutPurpose>().Select(purpose => purpose.Area()).ToList();
var areas = Enum.GetValues<LayoutPurpose>().Select(purpose => purpose.Area).ToList();
using (Assert.EnterMultipleScope())
{
Assert.That(areas, Is.Ordered, "The kinds of an area stand together, and the areas follow their own order.");
@@ -433,7 +433,7 @@ public sealed class PresentationTemplateTests
(PlaceholderRole.TEXT, 3, StackedHeadings.THIRD_TEXT, 8U),
}), "The copies are numbered after the slots of the layout, in reading order.");
Assert.That(slide.SlotCopies.Select(copy => copy.Slot.Capacity), Is.EqualTo(new TextCapacity?[] { new TextCapacity(1, 14), new TextCapacity(3, 25), new TextCapacity(1, 18), new TextCapacity(3, 25) }), "A copy holds what its size allows, as a moved slot does.");
Assert.That(slide.Slots.Select(slot => $"{slot.Role.CatalogName()} {slot.Ordinal}"), Is.EqualTo(new[] { "title 1", "label 1", "text 1", "label 2", "text 2", "label 3", "text 3" }));
Assert.That(slide.Slots.Select(slot => $"{slot.Role.CatalogName} {slot.Ordinal}"), Is.EqualTo(new[] { "title 1", "label 1", "text 1", "label 2", "text 2", "label 3", "text 3" }));
Assert.That(slide.Contents.Select(content => (content.Slot.Role, content.Text)), Is.EqualTo(new[]
{
(PlaceholderRole.TITLE, "Missions"),
@@ -500,7 +500,7 @@ public sealed class PresentationTemplateTests
using (Assert.EnterMultipleScope())
{
Assert.That(slide.SlotCopies.Select(copy => (copy.Slot.Ordinal, copy.Slot.Bounds)), Is.EqualTo(new[] { (2, upperRight), (3, lowerRight) }));
Assert.That(slide.Slots.Select(slot => $"{slot.Role.CatalogName()} {slot.Ordinal}"), Is.EqualTo(new[] { "title 1", "label 1", "text 2", "text 1", "text 3" }), "The slots of the layout keep their numbers; a copy goes where a reader comes across it.");
Assert.That(slide.Slots.Select(slot => $"{slot.Role.CatalogName} {slot.Ordinal}"), Is.EqualTo(new[] { "title 1", "label 1", "text 2", "text 1", "text 3" }), "The slots of the layout keep their numbers; a copy goes where a reader comes across it.");
}
}
+2 -2
View File
@@ -121,7 +121,7 @@ public sealed class SourceCodeLanguagesTests
[Test]
public void NoAliasNamesAFormatOfItsOwn()
{
// FileExportFormatExtensions.TryFromCodeFenceLanguage is asked first, so such an entry would never be found:
Assert.That(SourceCodeLanguages.Aliases.Where(alias => FileExportFormatExtensions.TryFromCodeFenceLanguage(alias, out _)), Is.Empty);
// FileExportFormat.TryFromCodeFenceLanguage is asked first, so such an entry would never be found:
Assert.That(SourceCodeLanguages.Aliases.Where(alias => FileExportFormat.TryFromCodeFenceLanguage(alias, out _)), Is.Empty);
}
}
+2 -2
View File
@@ -279,8 +279,8 @@ public sealed class SourcePassageTests
{
using (Assert.EnterMultipleScope())
{
Assert.That((Passage(HANDBOOK_URL, "Welcome.") with { Type = RetrievalContentType.TEXT_TRANSCRIPT }).IsTranscript(), Is.True);
Assert.That(Passage(HANDBOOK_URL, "Welcome.").IsTranscript(), Is.False);
Assert.That((Passage(HANDBOOK_URL, "Welcome.") with { Type = RetrievalContentType.TEXT_TRANSCRIPT }).IsTranscript, Is.True);
Assert.That(Passage(HANDBOOK_URL, "Welcome.").IsTranscript, Is.False);
}
}
@@ -65,7 +65,7 @@ public sealed class MailPassageTests
Assert.That(new[] { passage }.GetPassagesOf(MAIL_SOURCE), Is.EqualTo(new[] { passage }), "Only a passage under its source gets the eye next to it.");
Assert.That(passage.Path, Is.Empty, "The address of a mail names it for AI Studio only, so there is nothing to copy or open.");
Assert.That(passage.DataSourceName, Is.EqualTo(MAIL_SOURCE.Title));
Assert.That(passage.GetProvenanceKind(), Is.EqualTo(ProvenanceKind.MAIL));
Assert.That(passage.ProvenanceKind, Is.EqualTo(ProvenanceKind.MAIL));
}
}
}
@@ -140,7 +140,7 @@ public sealed class ToolOutboundDataTests : ToolRegistryTestBase
Assert.That(hasRun, Is.False, "The mail would already have left AI Studio.");
Assert.That(outcome.Trace.Status, Is.EqualTo(ToolInvocationTraceStatus.BLOCKED));
Assert.That(outcome.Content, Does.Contain("Work mailbox"), "The user learns which mailbox stands in the way.");
Assert.That(outcome.Content, Does.Contain(OutboundDataRestriction.ONLY_LINKS_FROM_CHAT.GetName()));
Assert.That(outcome.Content, Does.Contain(OutboundDataRestriction.ONLY_LINKS_FROM_CHAT.Name));
}
}
@@ -154,7 +154,7 @@ public sealed class ToolOutboundDataTests : ToolRegistryTestBase
{
Assert.That(message, Does.Contain("removed since"));
Assert.That(message, Does.Not.Contain("Private mailbox"), "Another mailbox did not restrict the chat.");
Assert.That(message, Does.Contain(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES.GetName()), "The restriction stays, so the user still learns why.");
Assert.That(message, Does.Contain(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES.Name), "The restriction stays, so the user still learns why.");
}
}
@@ -168,7 +168,7 @@ public sealed class ToolOutboundDataTests : ToolRegistryTestBase
{
Assert.That(message, Does.Contain("Confluence wiki"), "The user learns that the wiki stands in the way.");
Assert.That(message, Does.Not.Contain("mailbox"), "No mailbox restricted the chat.");
Assert.That(message, Does.Contain(OutboundDataRestriction.ONLY_LINKS_FROM_CHAT.GetName()));
Assert.That(message, Does.Contain(OutboundDataRestriction.ONLY_LINKS_FROM_CHAT.Name));
}
}
@@ -182,7 +182,7 @@ public sealed class ToolOutboundDataTests : ToolRegistryTestBase
{
Assert.That(message, Does.Contain("intranet.example.org"), "The user learns which page stands in the way.");
Assert.That(message, Does.Not.Contain("mailbox"), "No mailbox restricted the chat.");
Assert.That(message, Does.Contain(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES.GetName()));
Assert.That(message, Does.Contain(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES.Name));
}
}
@@ -196,7 +196,7 @@ public sealed class ToolOutboundDataTests : ToolRegistryTestBase
{
Assert.That(message, Does.Contain("Project files"), "The user learns which data source stands in the way.");
Assert.That(message, Does.Not.Contain("mailbox"), "No mailbox restricted the chat.");
Assert.That(message, Does.Contain(OutboundDataRestriction.ONLY_LINKS_FROM_CHAT.GetName()));
Assert.That(message, Does.Contain(OutboundDataRestriction.ONLY_LINKS_FROM_CHAT.Name));
}
}
@@ -210,7 +210,7 @@ public sealed class ToolOutboundDataTests : ToolRegistryTestBase
{
Assert.That(message, Does.Contain("removed since"));
Assert.That(message, Does.Not.Contain("Minutes"), "Another data source did not restrict the chat.");
Assert.That(message, Does.Contain(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES.GetName()), "The restriction stays, so the user still learns why.");
Assert.That(message, Does.Contain(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES.Name), "The restriction stays, so the user still learns why.");
}
}
@@ -37,7 +37,7 @@ public sealed class MailboxValidationTests
[TestCase(MailboxTransportSecurity.SSL_ON_CONNECT, ExpectedResult = 993)]
[TestCase(MailboxTransportSecurity.STARTTLS, ExpectedResult = 143)]
[TestCase(MailboxTransportSecurity.UNKNOWN, ExpectedResult = null)]
public int? EachEncryptionComesWithItsUsualPort(MailboxTransportSecurity transportSecurity) => transportSecurity.GetUsualPort();
public int? EachEncryptionComesWithItsUsualPort(MailboxTransportSecurity transportSecurity) => transportSecurity.UsualPort;
[TestCase(0, ExpectedResult = false)]
[TestCase(DataSourceValidation.MIN_ATTACHMENT_SIZE_MEGABYTES, ExpectedResult = true)]