Replace free-form fractional HUD widget positioning with a 4x8 int grid (col/row/colSpan/rowSpan), reject drag/resize moves that would overlap another visible widget, and re-place a re-enabled metric into the next free grid slot instead of stacking it on an occupier. Drag/resize now track a local pixel offset during the gesture and only snap/commit once at gesture end. HUD widget text is wrapped in FittedBox(scaleDown) so it never overflows and stays centered at any grid size.
181 lines
6.2 KiB
Dart
181 lines
6.2 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:rippr/src/config/config.dart';
|
|
import 'package:rippr/src/domain/models.dart';
|
|
import 'package:rippr/src/hud/hud_metric.dart';
|
|
import 'package:rippr/src/hud/hud_widget_layout.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
/// `Config` had no dedicated tests before V3-03 — every existing widget test leaves
|
|
/// `configProvider` null, so this is its first direct coverage.
|
|
void main() {
|
|
Future<Config> freshConfig([Map<String, Object> initial = const {}]) async {
|
|
SharedPreferences.setMockInitialValues(initial);
|
|
return Config.load();
|
|
}
|
|
|
|
group('unitSystem', () {
|
|
test('an explicit choice always wins over the locale default', () async {
|
|
final config = await freshConfig();
|
|
await config.setUnitSystem(UnitSystem.imperial);
|
|
expect(config.unitSystem, UnitSystem.imperial);
|
|
|
|
await config.setUnitSystem(UnitSystem.metric);
|
|
expect(config.unitSystem, UnitSystem.metric);
|
|
});
|
|
|
|
test('with nothing stored, falls back to a locale default without throwing', () async {
|
|
// The exact value is environment-dependent (it reads the real platform locale),
|
|
// so this asserts the fallback path is safe to reach, not a specific answer.
|
|
final config = await freshConfig();
|
|
expect(config.unitSystem, isA<UnitSystem>());
|
|
});
|
|
});
|
|
|
|
group('mapEnabled', () {
|
|
test('defaults to true', () async {
|
|
final config = await freshConfig();
|
|
expect(config.mapEnabled, isTrue);
|
|
});
|
|
|
|
test('round-trips through set/get', () async {
|
|
final config = await freshConfig();
|
|
await config.setMapEnabled(false);
|
|
expect(config.mapEnabled, isFalse);
|
|
});
|
|
});
|
|
|
|
group('mountedMode', () {
|
|
test('defaults to false', () async {
|
|
final config = await freshConfig();
|
|
expect(config.mountedMode, isFalse);
|
|
});
|
|
|
|
test('round-trips through set/get', () async {
|
|
final config = await freshConfig();
|
|
await config.setMountedMode(true);
|
|
expect(config.mountedMode, isTrue);
|
|
});
|
|
});
|
|
|
|
group('crashReportingEnabled', () {
|
|
test('defaults to false', () async {
|
|
final config = await freshConfig();
|
|
expect(config.crashReportingEnabled, isFalse);
|
|
});
|
|
|
|
test('round-trips through set/get', () async {
|
|
final config = await freshConfig();
|
|
await config.setCrashReportingEnabled(true);
|
|
expect(config.crashReportingEnabled, isTrue);
|
|
});
|
|
});
|
|
|
|
group('uploadEndpoint', () {
|
|
test('defaults to empty, not null or a placeholder', () async {
|
|
final config = await freshConfig();
|
|
expect(config.uploadEndpoint, '');
|
|
});
|
|
|
|
test('trims whitespace on write', () async {
|
|
final config = await freshConfig();
|
|
await config.setUploadEndpoint(' https://example.test/ingest ');
|
|
expect(config.uploadEndpoint, 'https://example.test/ingest');
|
|
});
|
|
});
|
|
|
|
group('hudLayout (UI-04)', () {
|
|
test('a fresh install gets a default layout for every metric', () async {
|
|
final config = await freshConfig();
|
|
final layout = config.hudLayout;
|
|
expect(layout.keys.toSet(), HudMetric.values.toSet());
|
|
});
|
|
|
|
test('round-trips an edited layout through set/get', () async {
|
|
final config = await freshConfig();
|
|
final edited = {
|
|
...config.hudLayout,
|
|
HudMetric.speed: const HudWidgetLayout(
|
|
metric: HudMetric.speed,
|
|
col: 2,
|
|
row: 3,
|
|
colSpan: 2,
|
|
rowSpan: 1,
|
|
visible: true,
|
|
),
|
|
};
|
|
|
|
await config.setHudLayout(edited);
|
|
final reloaded = config.hudLayout;
|
|
|
|
expect(reloaded[HudMetric.speed]!.col, 2);
|
|
expect(reloaded[HudMetric.speed]!.row, 3);
|
|
});
|
|
|
|
test('a metric missing from a saved layout (e.g. one added in a later app '
|
|
'version) falls back to its default rather than being omitted', () async {
|
|
SharedPreferences.setMockInitialValues({
|
|
'hud_layout': '{"speed": {"col": 1, "row": 1, "colSpan": 1, "rowSpan": 2, '
|
|
'"visible": true}}',
|
|
});
|
|
final config = await Config.load();
|
|
final layout = config.hudLayout;
|
|
|
|
expect(layout.keys.toSet(), HudMetric.values.toSet());
|
|
final expectedDefault = HudWidgetLayout.defaultFor(HudMetric.distance);
|
|
expect(layout[HudMetric.distance]!.col, expectedDefault.col);
|
|
expect(layout[HudMetric.distance]!.visible, expectedDefault.visible);
|
|
});
|
|
|
|
test('an old-shaped (pre-grid) saved layout falls back to defaults rather than '
|
|
'crashing', () async {
|
|
SharedPreferences.setMockInitialValues({
|
|
'hud_layout': '{"speed": {"x": 0.1, "y": 0.1, "width": 0.2, "height": 0.1, '
|
|
'"visible": true}}',
|
|
});
|
|
final config = await Config.load();
|
|
final layout = config.hudLayout;
|
|
|
|
expect(layout.keys.toSet(), HudMetric.values.toSet());
|
|
final expectedDefault = HudWidgetLayout.defaultFor(HudMetric.speed);
|
|
expect(layout[HudMetric.speed]!.col, expectedDefault.col);
|
|
});
|
|
|
|
test('corrupted stored JSON falls back to defaults rather than crashing',
|
|
() async {
|
|
SharedPreferences.setMockInitialValues({'hud_layout': 'not json at all'});
|
|
final config = await Config.load();
|
|
expect(config.hudLayout.keys.toSet(), HudMetric.values.toSet());
|
|
});
|
|
});
|
|
|
|
group('deviceId', () {
|
|
test('is generated once and then stable across reads', () async {
|
|
final config = await freshConfig();
|
|
final first = config.deviceId;
|
|
final second = config.deviceId;
|
|
expect(first, second);
|
|
expect(first, isNotEmpty);
|
|
});
|
|
|
|
test('is a v4 UUID', () async {
|
|
final config = await freshConfig();
|
|
// xxxxxxxx-xxxx-4xxx-{8,9,a,b}xxx-xxxxxxxxxxxx
|
|
final v4 = RegExp(
|
|
r'^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
|
|
);
|
|
expect(v4.hasMatch(config.deviceId), isTrue,
|
|
reason: 'got ${config.deviceId}');
|
|
});
|
|
|
|
test('persists across a fresh Config over the same preferences', () async {
|
|
SharedPreferences.setMockInitialValues({});
|
|
final first = await Config.load();
|
|
final id = first.deviceId;
|
|
|
|
final second = await Config.load();
|
|
expect(second.deviceId, id,
|
|
reason: 'a server distinguishing riders needs this to be stable');
|
|
});
|
|
});
|
|
}
|