feat: complete UM Trace iOS and field UI integration

This commit is contained in:
Brent Perteet
2026-08-23 20:54:03 -05:00
parent 16c2ada09b
commit 36f583169c
38 changed files with 548 additions and 212 deletions

View File

@@ -101,6 +101,11 @@ public sealed class Sprint3UiContractTests
var shell = File.ReadAllText(FromRoot("FieldLogger/AppShell.xaml"));
Assert.DoesNotContain("Title=\"Debug\"", shell, StringComparison.Ordinal);
Assert.DoesNotContain("views:DebugPage", shell, StringComparison.Ordinal);
Assert.DoesNotContain("<TabBar", shell, StringComparison.Ordinal);
Assert.DoesNotContain("Title=\"Home\"", shell, StringComparison.Ordinal);
Assert.True(shell.IndexOf("Title=\"Jobs\"", StringComparison.Ordinal) <
shell.IndexOf("Title=\"Map\"", StringComparison.Ordinal),
"Jobs must be the first primary route so it is selected at startup.");
}
[Fact]
@@ -113,6 +118,77 @@ public sealed class Sprint3UiContractTests
Assert.DoesNotContain("CaptureCommand", home, StringComparison.Ordinal);
}
[Fact]
public void Primary_pages_use_status_chrome_and_floating_menu()
{
foreach (var path in new[]
{
"FieldLogger/Views/JobsPage.xaml",
"FieldLogger/Views/MapPage.xaml",
"FieldLogger/Views/SettingsPage.xaml",
})
{
var page = File.ReadAllText(FromRoot(path));
Assert.Contains("controls:AppStatusBar", page, StringComparison.Ordinal);
Assert.Contains("controls:FloatingMenuButton", page, StringComparison.Ordinal);
Assert.Contains("Shell.NavBarIsVisible=\"False\"", page, StringComparison.Ordinal);
}
}
[Fact]
public void Settings_hides_manual_map_and_broker_configuration()
{
var settings = File.ReadAllText(FromRoot("FieldLogger/Views/SettingsPage.xaml"));
Assert.DoesNotContain("Google Maps API Key", settings, StringComparison.Ordinal);
Assert.DoesNotContain("Mqtt", settings, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain("Durable cloud sync", settings, StringComparison.Ordinal);
Assert.Contains("OpenDebugCommand", settings, StringComparison.Ordinal);
}
[Fact]
public void Map_has_no_heading_panel_and_fills_its_root_grid()
{
var map = File.ReadAllText(FromRoot("FieldLogger/Views/MapPage.xaml"));
Assert.DoesNotContain("LIVE MAP", map, StringComparison.Ordinal);
Assert.Contains("<ContentView x:Name=\"MapHost\"", map, StringComparison.Ordinal);
}
[Fact]
public void User_facing_app_name_is_UM_Trace()
{
var shell = File.ReadAllText(FromRoot("FieldLogger/AppShell.xaml"));
var settings = File.ReadAllText(FromRoot("FieldLogger/Views/SettingsPage.xaml"));
Assert.Contains("Title=\"UM Trace\"", shell, StringComparison.Ordinal);
Assert.Contains("UM Trace v", settings, StringComparison.Ordinal);
}
[Fact]
public void Jobs_uses_a_stable_full_height_refresh_layout()
{
var page = File.ReadAllText(FromRoot("FieldLogger/Views/JobsPage.xaml"));
var viewModel = File.ReadAllText(FromRoot("FieldLogger/ViewModels/JobsViewModel.cs"));
Assert.Contains("IsRefreshing=\"{Binding IsRefreshing, Mode=OneWay}\"", page, StringComparison.Ordinal);
Assert.DoesNotContain("IsRefreshing=\"{Binding IsBusy}\"", page, StringComparison.Ordinal);
Assert.Contains("ItemsUpdatingScrollMode=\"KeepItemsInView\"", page, StringComparison.Ordinal);
Assert.Contains("HorizontalOptions=\"Fill\" VerticalOptions=\"Fill\"", page, StringComparison.Ordinal);
Assert.DoesNotContain("Padding=\"16,16,16,88\"", page, StringComparison.Ordinal);
Assert.Contains("ApplySnapshot(items)", viewModel, StringComparison.Ordinal);
Assert.DoesNotContain("Jobs.Clear()", viewModel, StringComparison.Ordinal);
}
[Fact]
public void Device_deploy_rebuilds_Xaml_and_brand_assets_from_a_clean_iOS_output()
{
var script = File.ReadAllText(FromRoot("scripts/deploy-ios-device.sh"));
var cleanIndex = script.IndexOf("\"${dotnet_bin}\" clean", StringComparison.Ordinal);
var runIndex = script.IndexOf("\"${dotnet_bin}\" build", StringComparison.Ordinal);
Assert.True(cleanIndex >= 0, "Device deployment must clean stale partial MAUI outputs.");
Assert.True(runIndex > cleanIndex, "The clean must run before the device build.");
Assert.Contains("-p:RuntimeIdentifier=ios-arm64", script, StringComparison.Ordinal);
}
private static string FromRoot(string relativePath) => Path.Combine(FindRepositoryRoot(), relativePath);
private static string FindRepositoryRoot()