import 'package:flutter_test/flutter_test.dart'; import 'package:rippr/src/hud/hud_metric.dart'; import 'package:rippr/src/hud/hud_widget_layout.dart'; /// UI-04: the JSON round-trip and clamp math are exercised directly with fixed inputs /// -- no real gestures needed to test either, per the ticket's own Tests section. void main() { group('JSON round trip', () { test('encodes and decodes exactly', () { const original = HudWidgetLayout( metric: HudMetric.speed, x: 0.1, y: 0.2, width: 0.3, height: 0.15, visible: true, ); final decoded = HudWidgetLayout.fromJson(HudMetric.speed, original.toJson()); expect(decoded.x, original.x); expect(decoded.y, original.y); expect(decoded.width, original.width); expect(decoded.height, original.height); expect(decoded.visible, original.visible); }); test('a malformed entry falls back to the default rather than crashing', () { final decoded = HudWidgetLayout.fromJson(HudMetric.distance, {'x': 'not a number'}); final default_ = HudWidgetLayout.defaultFor(HudMetric.distance); expect(decoded.x, default_.x); expect(decoded.width, default_.width); }); test('a missing field falls back to the default rather than crashing', () { final decoded = HudWidgetLayout.fromJson(HudMetric.maxSpeed, {'x': 0.1, 'y': 0.1}); final default_ = HudWidgetLayout.defaultFor(HudMetric.maxSpeed); expect(decoded.x, default_.x); }); }); group('defaultFor', () { test('every metric gets a distinct default position', () { final positions = HudMetric.values .map(HudWidgetLayout.defaultFor) .map((l) => '${l.x},${l.y}') .toSet(); expect(positions.length, HudMetric.values.length, reason: 'no two metrics should default to overlapping positions'); }); test('the first four metrics (the mockup\'s fixed row) start visible', () { expect(HudWidgetLayout.defaultFor(HudMetric.speed).visible, isTrue); expect(HudWidgetLayout.defaultFor(HudMetric.distance).visible, isTrue); expect(HudWidgetLayout.defaultFor(HudMetric.elapsedTime).visible, isTrue); expect(HudWidgetLayout.defaultFor(HudMetric.maxSpeed).visible, isTrue); }); test('metrics beyond the fixed row start hidden', () { expect(HudWidgetLayout.defaultFor(HudMetric.avgSpeed).visible, isFalse); expect(HudWidgetLayout.defaultFor(HudMetric.elevationGain).visible, isFalse); }); test('every default is already within the valid clamp bounds', () { for (final metric in HudMetric.values) { final layout = HudWidgetLayout.defaultFor(metric); expect(layout.clamped().x, layout.x); expect(layout.clamped().y, layout.y); expect(layout.clamped().width, layout.width); expect(layout.clamped().height, layout.height); } }); }); group('clamped', () { const base = HudWidgetLayout( metric: HudMetric.speed, x: 0.5, y: 0.5, width: 0.3, height: 0.15, visible: true, ); test('a position dragged past the right/bottom edge is pulled back on-screen', () { final result = base.copyWith(x: 1.5, y: 1.5).clamped(); expect(result.x, 1.0 - base.width); expect(result.y, 1.0 - base.height); }); test('a position dragged past the left/top edge is pulled back on-screen', () { final result = base.copyWith(x: -0.5, y: -0.5).clamped(); expect(result.x, 0.0); expect(result.y, 0.0); }); test('a resize below the legibility floor is corrected up to the minimum', () { final result = base.copyWith(width: 0.01, height: 0.01).clamped(); expect(result.width, hudMinWidthFraction); expect(result.height, hudMinHeightFraction); }); test('a resize above the sane ceiling is corrected down to the maximum', () { final result = base.copyWith(width: 5.0, height: 5.0).clamped(); expect(result.width, hudMaxWidthFraction); expect(result.height, hudMaxHeightFraction); }); test('shrinking to fit happens before repositioning, so a widget resized past ' 'the edge shrinks rather than silently relocates', () { final result = base.copyWith(x: 0.9, width: 0.7).clamped(); expect(result.width, hudMaxWidthFraction); // x itself is still clamped against the (now-bounded) width so the widget // never sits partially off-screen either. expect(result.x + result.width, lessThanOrEqualTo(1.0)); }); test('an already-valid layout is unchanged', () { expect(base.clamped().x, base.x); expect(base.clamped().y, base.y); expect(base.clamped().width, base.width); expect(base.clamped().height, base.height); }); }); }