feat(ground-suite): add local vehicle catalog

This commit is contained in:
Onur Özkan committed 2026-08-13 20:02:42 +03:00
1 parent 2fe54fb053
commit cc9e9919db
18 files changed
+778 -6

No files matched your search

@@ -0,0 +1,77 @@
import 'dart:ui' show CheckedState;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:ground_suite/features/vehicle_workspace/domain/vehicle_catalog.dart';
import 'package:ground_suite/features/vehicle_workspace/presentation/widgets/vehicle_catalog_card.dart';
void main() {
test('every local vehicle type has a clear bundled profile image', () {
expect(supportedVehicleDefinitions, hasLength(6));
expect(
supportedVehicleDefinitions.map((definition) => definition.name),
containsAll(<String>[
'Quadcopter',
'Fixed-wing',
'Tiltrotor VTOL',
'Helicopter',
'Ground rover',
'Surface vessel',
]),
);
for (final definition in supportedVehicleDefinitions) {
expect(definition.id, isNotEmpty);
expect(definition.name, isNotEmpty);
expect(definition.media.imageAssetPath, isNotEmpty);
}
});
testWidgets('catalog card is a single keyboard-activatable radio control', (
tester,
) async {
var selections = 0;
final definition = supportedVehicleDefinitions.first;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: SingleChildScrollView(
child: Center(
child: SizedBox(
width: 760,
child: VehicleCatalogCard(
definition: definition,
selected: false,
onSelected: () => selections += 1,
),
),
),
),
),
),
);
final card = find.byType(VehicleCatalogCard);
final semantics = tester.getSemantics(card);
expect(semantics.flagsCollection.isChecked, CheckedState.isFalse);
expect(semantics.flagsCollection.isInMutuallyExclusiveGroup, isTrue);
expect(semantics.label, contains('local vehicle image, not telemetry'));
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
await tester.pump();
await tester.sendKeyEvent(LogicalKeyboardKey.space);
await tester.pump();
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
await tester.pump();
expect(selections, 2);
});
testWidgets('definition images are bundled locally', (tester) async {
for (final definition in supportedVehicleDefinitions) {
final image = await rootBundle.load(definition.media.imageAssetPath);
expect(image.lengthInBytes, greaterThan(0));
}
});
}
@@ -0,0 +1,136 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:ground_suite/features/fleet/domain/fleet_unit.dart';
import 'package:ground_suite/features/vehicle_workspace/domain/vehicle_catalog.dart';
import 'package:ground_suite/features/vehicle_workspace/presentation/vehicle_workspace_controller.dart';
void main() {
test('a reviewed definition creates a selected local fleet unit', () {
final container = ProviderContainer();
addTearDown(container.dispose);
final controller = container.read(vehicleWorkspaceProvider.notifier);
expect(container.read(vehicleWorkspaceProvider).hasUnits, isFalse);
final definition = supportedVehicleDefinitions.first;
expect(
controller.addUnit(
definition: definition,
localPlacement: const LocalScenePoint(x: .35, y: .45),
displayName: 'North survey',
),
isTrue,
);
expect(container.read(vehicleWorkspaceProvider).hasUnits, isTrue);
expect(
container.read(vehicleWorkspaceProvider).units.single.definition,
definition,
);
expect(
container.read(vehicleWorkspaceProvider).units.single.localUnitId,
'local-unit-1',
);
expect(
container.read(vehicleWorkspaceProvider).units.single.displayName,
'North survey',
);
expect(
controller.addUnit(
definition: definition,
localPlacement: const LocalScenePoint(x: .72, y: .45),
),
isTrue,
);
expect(container.read(vehicleWorkspaceProvider).units, hasLength(2));
expect(
container.read(vehicleWorkspaceProvider).units.last.localUnitId,
'local-unit-2',
);
});
test(
'rejects overlapping local placements and keeps a bounded workspace',
() {
final container = ProviderContainer();
addTearDown(container.dispose);
final controller = container.read(vehicleWorkspaceProvider.notifier);
final definition = supportedVehicleDefinitions.first;
const first = LocalScenePoint(x: .2, y: .2);
expect(
controller.addUnit(definition: definition, localPlacement: first),
isTrue,
);
expect(
controller.addUnit(definition: definition, localPlacement: first),
isFalse,
);
expect(
controller.addUnit(
definition: definition,
localPlacement: const LocalScenePoint(x: .23, y: .2),
),
isFalse,
);
expect(
controller.addUnit(
definition: definition,
localPlacement: const LocalScenePoint(x: .65, y: .7),
),
isTrue,
);
},
);
test(
'catalog additions receive a scattered local presentation placement',
() {
final container = ProviderContainer();
addTearDown(container.dispose);
final controller = container.read(vehicleWorkspaceProvider.notifier);
final definition = supportedVehicleDefinitions.first;
expect(controller.addUnitAutomatically(definition: definition), isTrue);
expect(controller.addUnitAutomatically(definition: definition), isTrue);
final units = container.read(vehicleWorkspaceProvider).units;
expect(units, hasLength(2));
expect(units.first.localPlacement, isNot(units.last.localPlacement));
for (final unit in units) {
expect(unit.localPlacement.x, inInclusiveRange(0.0, 1.0));
expect(unit.localPlacement.y, inInclusiveRange(0.0, 1.0));
}
},
);
test(
'local unit names are bounded workspace data, not vehicle configuration',
() {
final container = ProviderContainer();
addTearDown(container.dispose);
final controller = container.read(vehicleWorkspaceProvider.notifier);
expect(
controller.addUnitAutomatically(
definition: supportedVehicleDefinitions.first,
),
isTrue,
);
expect(
controller.renameUnit('local-unit-1', ' Ridge observer '),
isTrue,
);
expect(
container.read(vehicleWorkspaceProvider).units.single.displayName,
'Ridge observer',
);
expect(controller.renameUnit('local-unit-1', ' '), isFalse);
expect(
container.read(vehicleWorkspaceProvider).units.single.displayName,
'Ridge observer',
);
},
);
}