684 lines
25 KiB
Dart
684 lines
25 KiB
Dart
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:ground_suite/app/app.dart';
|
|
import 'package:ground_suite/app/app_shell.dart';
|
|
import 'package:ground_suite/app/theme.dart';
|
|
import 'package:ground_suite/features/command/presentation/tactical_command_page.dart';
|
|
import 'package:ground_suite/features/command/presentation/tactical_controller.dart';
|
|
import 'package:ground_suite/features/vehicle_workspace/domain/vehicle_catalog.dart';
|
|
import 'package:ground_suite/features/vehicle_workspace/presentation/vehicle_workspace_controller.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
void main() {
|
|
testWidgets('desktop command dashboard opens the grouped tactical controls', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1600, 960));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
final app = tester.widget<MaterialApp>(find.byType(MaterialApp));
|
|
expect(app.themeMode, ThemeMode.dark);
|
|
expect(find.text('TACTICAL COMMAND'), findsOneWidget);
|
|
expect(find.text('SIMULATED CAMERA CONCEPT'), findsOneWidget);
|
|
expect(find.text('DEMO-1021'), findsAtLeastNWidgets(1));
|
|
expect(find.byKey(const Key('units-menu')), findsOneWidget);
|
|
expect(find.byKey(const Key('cursor-menu')), findsOneWidget);
|
|
expect(find.byKey(const Key('view-menu')), findsOneWidget);
|
|
expect(find.text('UNITS'), findsNothing);
|
|
expect(find.text('MODE: SELECTION'), findsNothing);
|
|
expect(find.text('VIEW'), findsNothing);
|
|
expect(
|
|
tester.getSize(find.byKey(const Key('units-menu'))),
|
|
const Size(48, 48),
|
|
);
|
|
expect(
|
|
tester.getSize(find.byKey(const Key('cursor-menu'))),
|
|
const Size(48, 48),
|
|
);
|
|
expect(
|
|
tester.getSize(find.byKey(const Key('view-menu'))),
|
|
const Size(48, 48),
|
|
);
|
|
|
|
await tester.tap(find.byKey(const Key('units-menu')));
|
|
await tester.pump();
|
|
expect(find.text('Add local unit'), findsOneWidget);
|
|
expect(
|
|
find.text('Requires verified source-to-unit binding'),
|
|
findsOneWidget,
|
|
);
|
|
|
|
await tester.tap(find.text('Add local unit'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('UNIT HANGAR'), findsOneWidget);
|
|
expect(find.text('LOCAL SCENE'), findsAtLeastNWidgets(1));
|
|
expect(find.byKey(const Key('unit-hangar-strip')), findsOneWidget);
|
|
expect(
|
|
find.byKey(const Key('hangar-card-kaptan.vehicle.quadcopter.local')),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.byKey(const Key('hangar-card-kaptan.vehicle.fixed-wing.local')),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.byKey(const Key('hangar-card-kaptan.vehicle.tiltrotor-vtol.local')),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.byKey(const Key('hangar-card-kaptan.vehicle.helicopter.local')),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.byKey(const Key('hangar-card-kaptan.vehicle.rover.local')),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.byKey(const Key('hangar-card-kaptan.vehicle.surface-vessel.local')),
|
|
findsOneWidget,
|
|
);
|
|
|
|
await tester.tap(
|
|
find.byKey(const Key('hangar-card-kaptan.vehicle.quadcopter.local')),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('PREPARE UNIT'), findsOneWidget);
|
|
expect(find.text('QUADCOPTER-01'), findsNothing);
|
|
expect(
|
|
ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
).read(tacticalControllerProvider).unitDraft,
|
|
isNotNull,
|
|
);
|
|
|
|
final rectangle = await tester.startGesture(const Offset(620, 500));
|
|
await rectangle.moveTo(const Offset(760, 560));
|
|
await rectangle.up();
|
|
await tester.pumpAndSettle();
|
|
final draftState = ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
).read(tacticalControllerProvider);
|
|
expect(draftState.panel, TacticalPanel.unitHangar);
|
|
expect(draftState.unitDraft?.definition.name, 'Quadcopter');
|
|
|
|
await tester.tap(find.text('PREPARE UNIT'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('UNIT SETUP'), findsOneWidget);
|
|
expect(find.text('VEHICLE CONFIGURATION UNAVAILABLE'), findsOneWidget);
|
|
expect(
|
|
find.text(
|
|
'Unavailable until verified binding, compatible schema, command authority, and safe activation are available.',
|
|
),
|
|
findsOneWidget,
|
|
);
|
|
|
|
await tester.tap(find.text('ADD TO LOCAL SCENE'));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
).read(tacticalControllerProvider).mode,
|
|
isA<TacticalBrowseMode>(),
|
|
);
|
|
expect(find.text('QUADCOPTER-01'), findsAtLeastNWidgets(1));
|
|
|
|
await tester.tap(find.text('SET UP'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('LOCAL UNIT SETUP'), findsOneWidget);
|
|
expect(find.byKey(const Key('local-unit-name-field')), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('dark tactical theme is fixed and uses the palette tokens', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1600, 960));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
final context = tester.element(find.byType(AppShell));
|
|
expect(
|
|
tester.widget<MaterialApp>(find.byType(MaterialApp)).themeMode,
|
|
ThemeMode.dark,
|
|
);
|
|
expect(Theme.of(context).brightness, Brightness.dark);
|
|
expect(Theme.of(context).cardColor, panelBackground);
|
|
expect(
|
|
tester
|
|
.widget<Text>(
|
|
find.text(
|
|
'LOCAL SCENE DATA · NO VEHICLE LINK · COMMAND AUTHORITY UNAVAILABLE',
|
|
),
|
|
)
|
|
.style
|
|
?.color,
|
|
accentOrange,
|
|
);
|
|
});
|
|
|
|
testWidgets('selection console stays local and exposes no vehicle action', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1600, 960));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(const Key('unit-card-DEMO-1023')));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.text('LOCAL VISUAL PREVIEW · NO VEHICLE ACTION'),
|
|
findsOneWidget,
|
|
);
|
|
expect(find.text('DEPLOY'), findsNothing);
|
|
expect(find.text('CONFIGURE'), findsNothing);
|
|
|
|
await tester.tap(find.text('EVENT STREAM'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Event timeline'), findsOneWidget);
|
|
await tester.tap(find.text('Tactical workspace'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('TACTICAL COMMAND'), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('pencil cursor draws directly on the map with its toolbar', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1600, 960));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
).read(tacticalControllerProvider.notifier).beginPencil();
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('PENCIL CURSOR'), findsOneWidget);
|
|
expect(find.byKey(const Key('pencil-hue')), findsOneWidget);
|
|
expect(find.byKey(const Key('pencil-saturation')), findsOneWidget);
|
|
expect(find.byKey(const Key('pencil-brightness')), findsOneWidget);
|
|
expect(find.byKey(const Key('pencil-thickness')), findsOneWidget);
|
|
|
|
await tester.tap(find.byKey(const Key('pencil-swatch-282')));
|
|
await tester.drag(
|
|
find.byKey(const Key('pencil-thickness')),
|
|
const Offset(70, 0),
|
|
);
|
|
await tester.pump();
|
|
final pencil =
|
|
ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
).read(tacticalControllerProvider).mode
|
|
as TacticalPencilMode;
|
|
expect(pencil.hue, 282);
|
|
expect(pencil.thickness, greaterThan(3));
|
|
|
|
final stroke = await tester.startGesture(const Offset(620, 500));
|
|
await stroke.moveTo(const Offset(680, 540));
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
await stroke.moveTo(const Offset(740, 490));
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
await stroke.up();
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('PENCIL CURSOR'), findsOneWidget);
|
|
final container = ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
);
|
|
expect(container.read(tacticalControllerProvider).drawings, hasLength(1));
|
|
expect(find.text('DRAWING'), findsNothing);
|
|
expect(
|
|
tester.getSize(find.byKey(const Key('tactical-map-overlay-painter'))),
|
|
isNot(Size.zero),
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('marker cursor names a clicked local marker separately', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1600, 960));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(const Key('cursor-menu')));
|
|
await tester.pump();
|
|
await tester.tap(find.text('Marker cursor'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('MARKER CURSOR'), findsOneWidget);
|
|
expect(find.byKey(const Key('marker-hue')), findsOneWidget);
|
|
expect(find.byKey(const Key('marker-saturation')), findsOneWidget);
|
|
expect(find.byKey(const Key('marker-brightness')), findsOneWidget);
|
|
|
|
await tester.tap(find.byKey(const Key('marker-swatch-208')));
|
|
await tester.tapAt(const Offset(700, 520));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byKey(const Key('marker-name-field')), findsOneWidget);
|
|
|
|
await tester.enterText(
|
|
find.byKey(const Key('marker-name-field')),
|
|
'North staging note',
|
|
);
|
|
await tester.tap(find.text('SAVE MARKER'));
|
|
await tester.pumpAndSettle();
|
|
|
|
final state = ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
).read(tacticalControllerProvider);
|
|
expect(state.markers, hasLength(1));
|
|
expect(state.markers.single.name, 'North staging note');
|
|
expect(state.markers.single.hue, 208);
|
|
expect(state.drawings, isEmpty);
|
|
expect(find.text('North staging note'), findsAtLeastNWidgets(1));
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('a Pencil click alone never creates a marker or drawing', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1600, 960));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
final container = ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
);
|
|
container.read(tacticalControllerProvider.notifier).beginPencil();
|
|
await tester.pumpAndSettle();
|
|
await tester.tapAt(const Offset(700, 520));
|
|
await tester.pumpAndSettle();
|
|
|
|
final state = container.read(tacticalControllerProvider);
|
|
expect(state.drawings, isEmpty);
|
|
expect(state.markers, isEmpty);
|
|
expect(find.byKey(const Key('marker-name-field')), findsNothing);
|
|
});
|
|
|
|
testWidgets('map marker selection synchronizes the local selection console', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1600, 960));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(const Key('map-unit-DEMO-1023')));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('DEMO-1023'), findsAtLeastNWidgets(1));
|
|
expect(
|
|
ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
).read(tacticalControllerProvider).selectedUnitIds,
|
|
{'DEMO-1023'},
|
|
);
|
|
});
|
|
|
|
testWidgets('dragging the tactical canvas creates a local group selection', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1600, 960));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
final gesture = await tester.startGesture(const Offset(660, 520));
|
|
await gesture.moveTo(const Offset(960, 745));
|
|
await tester.pump();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
final selected = ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
).read(tacticalControllerProvider).selectedUnitIds;
|
|
expect(selected, containsAll(['DEMO-1021', 'DEMO-1023']));
|
|
expect(find.text('LOCAL GROUP SELECTION'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets(
|
|
'map deselection and hidden demo overlays never select invisibly',
|
|
(tester) async {
|
|
await tester.binding.setSurfaceSize(const Size(1600, 960));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(const Key('map-unit-DEMO-1023')));
|
|
await tester.pump();
|
|
await tester.tapAt(const Offset(600, 500));
|
|
await tester.pumpAndSettle();
|
|
|
|
final container = ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
);
|
|
expect(
|
|
container.read(tacticalControllerProvider).selectedUnitIds,
|
|
isEmpty,
|
|
);
|
|
|
|
await tester.tap(find.byKey(const Key('view-menu')));
|
|
await tester.pump();
|
|
await tester.tap(find.text('Hide Drones'));
|
|
await tester.pumpAndSettle();
|
|
|
|
final gesture = await tester.startGesture(const Offset(660, 520));
|
|
await gesture.moveTo(const Offset(960, 745));
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
container.read(tacticalControllerProvider).selectedUnitIds,
|
|
isEmpty,
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets('selection cursor ends pencil mode and restores selection', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1600, 960));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(const Key('cursor-menu')));
|
|
await tester.pump();
|
|
await tester.tap(find.text('Pencil cursor'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('PENCIL CURSOR'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('SELECTION CURSOR'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('PENCIL CURSOR'), findsNothing);
|
|
expect(FocusManager.instance.primaryFocus, isNotNull);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('hangar selection prepares a local unit before confirmation', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1600, 960));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(const Key('units-menu')));
|
|
await tester.pump();
|
|
await tester.tap(find.text('Add local unit'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(
|
|
find.byKey(const Key('hangar-card-kaptan.vehicle.quadcopter.local')),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
final scope = ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
);
|
|
expect(scope.read(tacticalControllerProvider).unitDraft, isNotNull);
|
|
expect(scope.read(vehicleWorkspaceProvider).units, isEmpty);
|
|
|
|
await tester.tap(find.text('PREPARE UNIT'));
|
|
await tester.pumpAndSettle();
|
|
final name = find.byKey(const Key('local-unit-name-field'));
|
|
await tester.tap(name);
|
|
await tester.enterText(name, 'North survey');
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('ADD TO LOCAL SCENE'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('North survey'), findsAtLeastNWidgets(1));
|
|
expect(scope.read(vehicleWorkspaceProvider).units, hasLength(1));
|
|
expect(
|
|
scope.read(vehicleWorkspaceProvider).units.single.displayName,
|
|
'North survey',
|
|
);
|
|
expect(
|
|
ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
).read(tacticalControllerProvider).mode,
|
|
isA<TacticalBrowseMode>(),
|
|
);
|
|
});
|
|
|
|
testWidgets('hangar keeps a draft on back and blocks an invalid local name', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1600, 960));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(const Key('units-menu')));
|
|
await tester.pump();
|
|
await tester.tap(find.text('Add local unit'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(
|
|
find.byKey(const Key('hangar-card-kaptan.vehicle.fixed-wing.local')),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('PREPARE UNIT'));
|
|
await tester.pumpAndSettle();
|
|
|
|
final name = find.byKey(const Key('local-unit-name-field'));
|
|
await tester.tap(name);
|
|
await tester.enterText(name, ' ');
|
|
await tester.tap(find.text('ADD TO LOCAL SCENE'));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.text('Enter a local unit name of up to 32 characters.'),
|
|
findsOneWidget,
|
|
);
|
|
final scope = ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
);
|
|
expect(scope.read(vehicleWorkspaceProvider).units, isEmpty);
|
|
|
|
await tester.tap(find.text('BACK TO HANGAR'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('FIXED-WING SELECTED'), findsOneWidget);
|
|
expect(
|
|
scope.read(tacticalControllerProvider).unitDraft?.definition.name,
|
|
'Fixed-wing',
|
|
);
|
|
});
|
|
|
|
testWidgets('hangar supports arrow selection and Escape focus return', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1600, 960));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(const Key('units-menu')));
|
|
await tester.pump();
|
|
await tester.tap(find.text('Add local unit'));
|
|
await tester.pumpAndSettle();
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
|
await tester.pumpAndSettle();
|
|
|
|
final scope = ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
);
|
|
expect(
|
|
scope.read(tacticalControllerProvider).unitDraft?.definition.name,
|
|
'Fixed-wing',
|
|
);
|
|
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('UNIT HANGAR'), findsNothing);
|
|
final trigger = find.descendant(
|
|
of: find.byKey(const Key('units-menu')),
|
|
matching: find.byType(TextButton),
|
|
);
|
|
expect(tester.widget<TextButton>(trigger).focusNode?.hasFocus, isTrue);
|
|
});
|
|
|
|
testWidgets('hangar and setup remain usable at 200 percent text scale', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1440, 960));
|
|
tester.binding.platformDispatcher.textScaleFactorTestValue = 2;
|
|
addTearDown(() {
|
|
tester.binding.setSurfaceSize(null);
|
|
tester.binding.platformDispatcher.clearTextScaleFactorTestValue();
|
|
});
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
final scope = ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
);
|
|
final controller = scope.read(tacticalControllerProvider.notifier);
|
|
controller.showUnitHangar();
|
|
await tester.pumpAndSettle();
|
|
expect(find.byKey(const Key('unit-hangar-strip')), findsOneWidget);
|
|
controller.chooseUnitDefinition(
|
|
supportedVehicleDefinitions.first,
|
|
displayName: 'QUADCOPTER-01',
|
|
);
|
|
controller.beginUnitSetup();
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('UNIT SETUP'), findsOneWidget);
|
|
expect(find.byKey(const Key('local-unit-name-field')), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('hangar stays available in the short desktop arrangement', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1600, 760));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
final scope = ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
);
|
|
scope.read(tacticalControllerProvider.notifier).showUnitHangar();
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('UNIT HANGAR'), findsOneWidget);
|
|
expect(find.byKey(const Key('unit-hangar-strip')), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('secondary click cancels the pencil cursor', (tester) async {
|
|
await tester.binding.setSurfaceSize(const Size(1600, 960));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(const Key('cursor-menu')));
|
|
await tester.pump();
|
|
await tester.tap(find.text('Pencil cursor'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('PENCIL CURSOR'), findsOneWidget);
|
|
|
|
final pointer = TestPointer(42, PointerDeviceKind.mouse);
|
|
await tester.sendEventToBinding(
|
|
pointer.down(const Offset(700, 520), buttons: kSecondaryButton),
|
|
);
|
|
await tester.sendEventToBinding(pointer.up());
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('PENCIL CURSOR'), findsNothing);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('keyboard pencil keeps a visible draft before finishing', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1600, 960));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(const Key('cursor-menu')));
|
|
await tester.pump();
|
|
await tester.tap(find.text('Pencil cursor'));
|
|
await tester.pumpAndSettle();
|
|
|
|
for (var index = 0; index < 3; index++) {
|
|
await tester.tap(find.text('ADD LOCAL POINT'));
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
|
}
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('LOCAL POINTS: 3'), findsOneWidget);
|
|
expect(find.text('FINISH DRAWING'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('FINISH DRAWING'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('PENCIL CURSOR'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('compact command dashboard remains usable without overflow', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(480, 800));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('SIMULATED CAMERA CONCEPT'), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('command dashboard handles 200 percent text scale', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1440, 960));
|
|
tester.binding.platformDispatcher.textScaleFactorTestValue = 2;
|
|
addTearDown(() {
|
|
tester.binding.setSurfaceSize(null);
|
|
tester.binding.platformDispatcher.clearTextScaleFactorTestValue();
|
|
});
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
find.text(
|
|
'LOCAL SCENE DATA · NO VEHICLE LINK · COMMAND AUTHORITY UNAVAILABLE',
|
|
),
|
|
findsOneWidget,
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('pencil toolbar reflows at 200 percent text scale', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1440, 960));
|
|
tester.binding.platformDispatcher.textScaleFactorTestValue = 2;
|
|
addTearDown(() {
|
|
tester.binding.setSurfaceSize(null);
|
|
tester.binding.platformDispatcher.clearTextScaleFactorTestValue();
|
|
});
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
ProviderScope.containerOf(
|
|
tester.element(find.byType(TacticalCommandPage)),
|
|
).read(tacticalControllerProvider.notifier).beginPencil();
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('PENCIL CURSOR'), findsOneWidget);
|
|
expect(find.byKey(const Key('pencil-thickness')), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('short desktop window uses the non-overlapping command layout', (
|
|
tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(const Size(1366, 768));
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
await tester.pumpWidget(const GroundSuiteApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('SIMULATED CAMERA CONCEPT'), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
}
|