151 lines
10 KiB
C#
151 lines
10 KiB
C#
using System;
|
|
using System.Linq;
|
|
using TrajectoryPlanningVisualization;
|
|
|
|
namespace TrajectoryPlanningVisualizationVerificationHost;
|
|
|
|
internal static class WebAssetChecks
|
|
{
|
|
public static void Run()
|
|
{
|
|
BuildsDeterministicSmokeSnapshots();
|
|
|
|
string html = EmbeddedWebAssets.ReadText("index.html");
|
|
string css = EmbeddedWebAssets.ReadText("app.css");
|
|
string js = EmbeddedWebAssets.ReadText("app.js");
|
|
|
|
Verification.True(html.Contains("路径总览"), "overview Chinese title");
|
|
Verification.True(html.Contains("LS / ST"), "LS/ST tab");
|
|
Verification.True(html.Contains("曲率与运动学"), "kinematics tab");
|
|
Verification.True(html.Contains("周期历史"), "history tab");
|
|
Verification.True(html.Contains("生效配置"), "configuration panel");
|
|
Verification.True(css.Contains("--current-trajectory: #1769aa"), "scientific current color");
|
|
Verification.True(css.Contains("stroke-width: 1.1"), "thin scientific line");
|
|
Verification.True(css.Contains("main > section[hidden] { display: none !important; }"),
|
|
"hidden sections cannot be overridden by grid rules");
|
|
Verification.True(css.Contains(".axis-tick-y"), "SVG y ticks have a semantic class");
|
|
Verification.True(css.Contains(".axis-label-y"), "SVG y labels have a semantic class");
|
|
Verification.True(css.Contains(".world-empty-state"), "empty overview has a semantic state");
|
|
Verification.True(css.Contains(".world-coarse"), "coarse path semantic class");
|
|
Verification.True(css.Contains(".world-local-g2"), "Local G2 semantic class");
|
|
Verification.True(css.Contains(".world-segment-active"), "active direction semantic class");
|
|
Verification.True(css.Contains(".world-current"), "current trajectory semantic class");
|
|
Verification.True(css.Contains(".world-coordinate-grid"),
|
|
"overview exposes a metric grid semantic class");
|
|
Verification.True(css.Contains(".world-axis-zero"),
|
|
"overview exposes a zero-axis semantic class");
|
|
Verification.True(css.Contains(".world-axis-tick"),
|
|
"overview exposes coordinate tick semantics");
|
|
Verification.True(css.Contains(".world-axis-label"),
|
|
"overview exposes coordinate axis-label semantics");
|
|
Verification.True(js.Contains("function niceWorldStep("),
|
|
"overview selects readable metric tick steps");
|
|
Verification.True(js.Contains("function worldMetrics("),
|
|
"overview derives stable world-to-screen metrics");
|
|
Verification.True(js.Contains("function renderWorldCoordinates("),
|
|
"overview renders its own metric coordinate system");
|
|
Verification.True(js.Contains("X (m)") && js.Contains("Y (m)"),
|
|
"overview labels both metric axes");
|
|
Verification.True(css.Contains("--world-smoothed-path"),
|
|
"overview defines a dedicated smoothed-path color");
|
|
Verification.True(css.Contains("--world-em-trajectory"),
|
|
"overview defines a dedicated EM-path color");
|
|
Verification.True(css.Contains(".world-marker-shape"),
|
|
"overview marker shapes use a shared screen-space class");
|
|
Verification.True(css.Contains(".marker-smooth-start"),
|
|
"overview styles the smoothed-path start marker");
|
|
Verification.True(js.Contains("function markerTransform("),
|
|
"overview anchors fixed-size markers in world coordinates");
|
|
Verification.True(js.Contains("function appendWorldMarker("),
|
|
"overview centralizes fixed-size marker rendering");
|
|
Verification.True(!js.Contains("const corners = [[.38, .18]"),
|
|
"vehicle marker no longer uses a large world-size rectangle");
|
|
Verification.True(!css.Contains(".marker-vehicle .vehicle-outline { fill: none; stroke: #20252b; stroke-width: .7; }"),
|
|
"vehicle outline no longer uses a world-scale heavy stroke");
|
|
Verification.True(!css.Contains(".marker-vehicle .vehicle-heading { stroke: #20252b; stroke-width: .8; }"),
|
|
"vehicle marker no longer uses world-scale heavy strokes");
|
|
Verification.True(css.Contains(".marker-vehicle"), "vehicle marker semantic class");
|
|
Verification.True(css.Contains(".marker-plan-start"), "plan start marker semantic class");
|
|
Verification.True(css.Contains(".marker-plan-start { fill: none;"),
|
|
"plan start marker remains hollow when it overlaps the smoothed-path start");
|
|
Verification.True(css.Contains(".marker-gear-switch"), "gear-switch marker semantic class");
|
|
Verification.True(css.Contains(".marker-final-goal"), "final goal marker semantic class");
|
|
Verification.True(js.Contains("末点后无时间区间"), "jerk terminal explanation");
|
|
Verification.True(js.Contains("__TRAJECTORY_VISUALIZATION_TEST__"), "test hook flag is explicit");
|
|
Verification.True(js.Contains("Object.freeze"), "test hooks are frozen");
|
|
Verification.True(js.Contains("未收到 Local G2 路径"), "empty overview names the missing source");
|
|
Verification.True(html.Contains("occupancy-grid"), "single occupancy canvas exists");
|
|
Verification.True(html.Contains("world-overlay"), "SVG trajectory overlay exists");
|
|
Verification.True(html.Contains("id=\"world-legend\""),
|
|
"overview contains a screen-space legend host");
|
|
Verification.True(html.Contains("aria-label=\"路径图例\""),
|
|
"overview legend has a Chinese accessible name");
|
|
Verification.True(css.Contains(".world-legend"),
|
|
"overview legend has a screen-space visual container");
|
|
Verification.True(css.Contains(".world-legend-swatch"),
|
|
"overview legend exposes semantic line and marker samples");
|
|
Verification.True(js.Contains("function renderWorldLegend("),
|
|
"overview builds legend entries from current snapshot data");
|
|
Verification.True(js.Contains("legendChinese"),
|
|
"overview reuses exported Chinese polyline legends");
|
|
Verification.True(
|
|
js.Contains("smoothStartMarker.labelChinese || \"平滑路径起点\"") &&
|
|
js.Contains("planStartMarker.labelChinese || \"EM 规划起点\"") &&
|
|
js.Contains("vehicleMarker.labelChinese || \"当前车辆\"") &&
|
|
js.Contains("gearMarker.labelChinese || \"换向点 / s_end\"") &&
|
|
js.Contains("goalMarker.labelChinese || \"平滑路径终点\""),
|
|
"overview marker legends prefer the first matching marker label with canonical fallbacks");
|
|
Verification.True(js.Contains("const hasDynamicActiveSegment = dynamicLines.some("),
|
|
"overview detects whether the frame supplies a dynamic active segment");
|
|
Verification.True(js.Contains(
|
|
".filter(segment => !hasDynamicActiveSegment || segment.segmentIndex !== frame.activeSegmentIndex)"),
|
|
"overview omits the matching static segment when its dynamic active segment exists");
|
|
Verification.True(js.Contains("if (active)") &&
|
|
!js.Contains("if (active || directionSegments.length)"),
|
|
"overview legend activates the current direction segment from dynamic data only");
|
|
int renderWorldStart = js.IndexOf("function renderWorld()", StringComparison.Ordinal);
|
|
int renderDynamicLines = js.IndexOf("const dynamicLines = safeArray(frame.dynamicPolylines);",
|
|
renderWorldStart, StringComparison.Ordinal);
|
|
int renderStaticSegments = js.IndexOf("safeArray(snapshot.directionSegments)",
|
|
renderDynamicLines, StringComparison.Ordinal);
|
|
Verification.True(renderWorldStart >= 0 && renderDynamicLines > renderWorldStart &&
|
|
renderStaticSegments > renderDynamicLines,
|
|
"overview derives dynamic lines before rendering static direction segments");
|
|
Verification.True(js.Contains("atob"), "compact occupancy bitset is decoded in browser");
|
|
Verification.True(!html.Contains("http://") && !html.Contains("https://"), "page has no CDN URL");
|
|
}
|
|
|
|
private static void BuildsDeterministicSmokeSnapshots()
|
|
{
|
|
PlanningVisualizationStaticSnapshot staticSnapshot = SampleSnapshotFactory.CreateStaticSnapshot();
|
|
PlanningVisualizationDynamicSnapshot dynamicSnapshot = SampleSnapshotFactory.CreateDynamicSnapshot(1L);
|
|
|
|
Verification.True(staticSnapshot.StaticPolylines.Any(line => line.Kind == "coarse"),
|
|
"smoke snapshot exposes the coarse comparison path");
|
|
Verification.True(staticSnapshot.StaticPolylines.Any(line => line.Kind == "local-g2"),
|
|
"smoke snapshot exposes the smoothed comparison path");
|
|
Verification.True(staticSnapshot.StaticMarkers.Any(marker => marker.Kind == "smooth-start"),
|
|
"smoke snapshot exposes the smoothed-path start marker");
|
|
Verification.Equal(1, dynamicSnapshot.DynamicPolylines.Count(line => line.Kind == "active-segment"),
|
|
"smoke snapshot has exactly one dynamic active segment");
|
|
VisualizationMarker smoothStart = staticSnapshot.StaticMarkers.Single(marker => marker.Kind == "smooth-start");
|
|
VisualizationMarker planStart = dynamicSnapshot.DynamicMarkers.Single(marker => marker.Kind == "plan-start");
|
|
VisualizationPolyline current = dynamicSnapshot.DynamicPolylines.Single(line => line.Kind == "current");
|
|
Verification.NearlyEqual(smoothStart.Position.X, planStart.Position.X,
|
|
"smoke snapshot overlaps smoothed-path and plan-start x coordinates");
|
|
Verification.NearlyEqual(smoothStart.Position.Y, planStart.Position.Y,
|
|
"smoke snapshot overlaps smoothed-path and plan-start y coordinates");
|
|
Verification.NearlyEqual(current.Points[0].X, planStart.Position.X,
|
|
"smoke snapshot aligns plan-start x with the current EM trajectory start");
|
|
Verification.NearlyEqual(current.Points[0].Y, planStart.Position.Y,
|
|
"smoke snapshot aligns plan-start y with the current EM trajectory start");
|
|
Verification.Equal(2, staticSnapshot.DirectionSegments.Count, "smoke snapshot has two direction segments");
|
|
Verification.True(staticSnapshot.StaticMarkers.Any(marker => marker.Kind == "gear-switch"), "smoke snapshot has a gear marker");
|
|
Verification.True(staticSnapshot.ConfigurationGroups.Count > 0, "smoke snapshot has effective configuration");
|
|
foreach (string chartId in new[] { "ls", "st", "curvature-s", "curvature-t", "velocity-t", "acceleration-t", "jerk-t", "yaw-rate-t" })
|
|
{
|
|
Verification.True(dynamicSnapshot.Charts.Any(chart => chart.Id == chartId), "smoke snapshot includes chart " + chartId);
|
|
}
|
|
}
|
|
}
|