feat(sprint-3): apply Survey Teal field UI
This commit is contained in:
130
tests/FieldLogger.Tests/Sprint3UiContractTests.cs
Normal file
130
tests/FieldLogger.Tests/Sprint3UiContractTests.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace FieldLogger.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Headless guardrails for the Sprint 3 MAUI screen integration. These deliberately inspect
|
||||
/// XAML source so the QA gate can verify token use and accessibility basics without a MAUI
|
||||
/// simulator or device workload.
|
||||
/// </summary>
|
||||
public sealed class Sprint3UiContractTests
|
||||
{
|
||||
private static readonly string[] MigratedScreens =
|
||||
[
|
||||
"FieldLogger/Views/HomePage.xaml",
|
||||
"FieldLogger/Views/JobsPage.xaml",
|
||||
"FieldLogger/Views/JobDetailPage.xaml",
|
||||
"FieldLogger/Views/MapPage.xaml",
|
||||
];
|
||||
|
||||
[Fact]
|
||||
public void Migrated_screens_use_only_semantic_color_resources()
|
||||
{
|
||||
var hardCodedColor = new Regex(@"#[0-9a-fA-F]{3,8}\b", RegexOptions.CultureInvariant);
|
||||
foreach (var path in MigratedScreens)
|
||||
{
|
||||
var text = File.ReadAllText(FromRoot(path));
|
||||
Assert.False(hardCodedColor.IsMatch(text), $"Page-level color literal found in {path}");
|
||||
Assert.DoesNotContain("TextColor=\"Gray\"", text, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("DodgerBlue", text, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("IndianRed", text, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Every_referenced_Survey_Teal_token_exists_in_generated_resources()
|
||||
{
|
||||
var resourceText = string.Concat(
|
||||
File.ReadAllText(FromRoot("FieldLogger/Resources/Styles/GeneratedTokens.xaml")),
|
||||
File.ReadAllText(FromRoot("FieldLogger/Resources/Styles/Styles.xaml")));
|
||||
var defined = Regex.Matches(resourceText, "x:Key=\\\"(?<key>Ul[^\\\"]+)\\\"")
|
||||
.Select(match => match.Groups["key"].Value)
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
var resourceReference = new Regex(@"\{(?:Static|Dynamic)Resource (?<key>Ul[^}\s]+)\}");
|
||||
|
||||
foreach (var path in MigratedScreens.Append("FieldLogger/AppShell.xaml"))
|
||||
{
|
||||
foreach (Match match in resourceReference.Matches(File.ReadAllText(FromRoot(path))))
|
||||
{
|
||||
var key = match.Groups["key"].Value;
|
||||
Assert.Contains(key, defined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void App_loads_generated_tokens_before_shared_component_styles()
|
||||
{
|
||||
var app = File.ReadAllText(FromRoot("FieldLogger/App.xaml"));
|
||||
var generatedIndex = app.IndexOf("GeneratedTokens.xaml", StringComparison.Ordinal);
|
||||
var stylesIndex = app.IndexOf("Styles.xaml", StringComparison.Ordinal);
|
||||
Assert.True(generatedIndex >= 0, "App.xaml must merge GeneratedTokens.xaml.");
|
||||
Assert.True(stylesIndex > generatedIndex, "Generated tokens must load before component styles.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Migrated_screens_consume_shared_component_styles()
|
||||
{
|
||||
var migrated = string.Concat(MigratedScreens.Select(path => File.ReadAllText(FromRoot(path))));
|
||||
Assert.Contains("UlCard", migrated, StringComparison.Ordinal);
|
||||
Assert.Contains("UlJobRow", migrated, StringComparison.Ordinal);
|
||||
Assert.Contains("UlStatusBadge", migrated, StringComparison.Ordinal);
|
||||
Assert.Contains("UlButtonSecondary", migrated, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Every_page_button_declares_at_least_the_minimum_touch_target()
|
||||
{
|
||||
var accepted = new[]
|
||||
{
|
||||
"{StaticResource UlTouchMinimum}",
|
||||
"{StaticResource UlTouchComfortable}",
|
||||
"{StaticResource UlTouchPrimary}",
|
||||
};
|
||||
|
||||
foreach (var path in MigratedScreens)
|
||||
{
|
||||
var document = XDocument.Load(FromRoot(path));
|
||||
foreach (var button in document.Descendants().Where(e => e.Name.LocalName == "Button"))
|
||||
{
|
||||
var target = button.Attribute("MinimumHeightRequest")?.Value;
|
||||
Assert.Contains(target, accepted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Production_shell_does_not_expose_Debug_as_a_primary_tab()
|
||||
{
|
||||
var shell = File.ReadAllText(FromRoot("FieldLogger/AppShell.xaml"));
|
||||
Assert.DoesNotContain("Title=\"Debug\"", shell, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("views:DebugPage", shell, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Home_explains_real_receiver_capture_without_inventing_an_app_command()
|
||||
{
|
||||
var home = File.ReadAllText(FromRoot("FieldLogger/Views/HomePage.xaml"));
|
||||
var state = File.ReadAllText(FromRoot("FieldLogger/ViewModels/HomeViewModel.cs"));
|
||||
Assert.Contains("CAPTURE FROM RECEIVER", home, StringComparison.Ordinal);
|
||||
Assert.Contains("Press LOG on the locating receiver", state, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("CaptureCommand", home, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static string FromRoot(string relativePath) => Path.Combine(FindRepositoryRoot(), relativePath);
|
||||
|
||||
private static string FindRepositoryRoot()
|
||||
{
|
||||
for (var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
directory is not null;
|
||||
directory = directory.Parent)
|
||||
{
|
||||
if (File.Exists(Path.Combine(directory.FullName, "FieldLogger.sln")))
|
||||
return directory.FullName;
|
||||
}
|
||||
|
||||
throw new DirectoryNotFoundException("Could not find the FieldLogger repository root.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user