255 lines
8.8 KiB
Dart
255 lines
8.8 KiB
Dart
import 'dart:async';
|
||
import 'package:fpdart/fpdart.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||
import 'package:flutter_test/flutter_test.dart';
|
||
import 'package:maibu_satabot_v2/core/di/injection.dart';
|
||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||
import 'package:maibu_satabot_v2/features/device_control_mode/domain/entities/device_control_mode.dart';
|
||
import 'package:maibu_satabot_v2/features/device_control_mode/domain/usecases/device_control_mode_usecases.dart';
|
||
import 'package:maibu_satabot_v2/features/device_control_mode/presentation/cubit/control_mode_entry_cubit.dart';
|
||
import 'package:maibu_satabot_v2/features/device_control_mode/presentation/widgets/control_mode_entry_gate.dart';
|
||
import 'support/control_mode_fakes.dart';
|
||
|
||
final _translations = <String, AppLocalizations>{};
|
||
|
||
class _LoadedTranslations extends LocalizationsDelegate<AppLocalizations> {
|
||
const _LoadedTranslations();
|
||
@override
|
||
bool isSupported(Locale locale) =>
|
||
_translations.containsKey(locale.toString());
|
||
@override
|
||
Future<AppLocalizations> load(Locale locale) =>
|
||
SynchronousFuture(_translations[locale.toString()]!);
|
||
@override
|
||
bool shouldReload(_LoadedTranslations old) => false;
|
||
}
|
||
|
||
Widget app(Widget home, {Locale locale = const Locale('zh', 'CN')}) =>
|
||
MaterialApp(
|
||
locale: locale,
|
||
supportedLocales: const [Locale('zh', 'CN'), Locale('en', 'US')],
|
||
localizationsDelegates: const [
|
||
_LoadedTranslations(),
|
||
GlobalMaterialLocalizations.delegate,
|
||
GlobalWidgetsLocalizations.delegate,
|
||
GlobalCupertinoLocalizations.delegate,
|
||
],
|
||
home: home,
|
||
);
|
||
|
||
Future<void> openDialog(
|
||
WidgetTester tester,
|
||
ControlModeEntryCubit cubit,
|
||
ValueChanged<bool> result, {
|
||
Locale locale = const Locale('zh', 'CN'),
|
||
Size size = const Size(800, 360),
|
||
}) async {
|
||
tester.view.physicalSize = size;
|
||
tester.view.devicePixelRatio = 1;
|
||
addTearDown(tester.view.resetPhysicalSize);
|
||
addTearDown(tester.view.resetDevicePixelRatio);
|
||
await tester.pumpWidget(
|
||
app(
|
||
Builder(
|
||
builder: (context) => Scaffold(
|
||
body: TextButton(
|
||
onPressed: () async => result(
|
||
await showControlModeEntryDialog(
|
||
context,
|
||
deviceId: cubit.deviceId,
|
||
target: cubit.target,
|
||
deviceName: '测试车',
|
||
cubit: cubit,
|
||
),
|
||
),
|
||
child: const Text('open'),
|
||
),
|
||
),
|
||
),
|
||
locale: locale,
|
||
),
|
||
);
|
||
await tester.pumpAndSettle();
|
||
await tester.tap(find.text('open'));
|
||
await tester.pump();
|
||
await tester.pump(const Duration(milliseconds: 250));
|
||
}
|
||
|
||
void main() {
|
||
TestWidgetsFlutterBinding.ensureInitialized();
|
||
setUpAll(() async {
|
||
// 真实资产I/O在测试假时钟外完成,弹窗测试使用同一套真实中英文文案。
|
||
for (final locale in [const Locale('zh', 'CN'), const Locale('en', 'US')]) {
|
||
final translations = AppLocalizations(locale);
|
||
await translations.load();
|
||
_translations[locale.toString()] = translations;
|
||
await GlobalMaterialLocalizations.delegate.load(locale);
|
||
await GlobalCupertinoLocalizations.delegate.load(locale);
|
||
}
|
||
});
|
||
late FakeModes repo;
|
||
late ControlModeEntryCubit cubit;
|
||
setUp(() {
|
||
repo = FakeModes();
|
||
cubit = modeCubit(repo);
|
||
});
|
||
tearDown(() async {
|
||
if (!cubit.isClosed) await cubit.close();
|
||
await repo.reports.close();
|
||
await sl.reset();
|
||
});
|
||
|
||
testWidgets('进入弹窗显示实时模式,已匹配时确定仅进入、不调用切换', (tester) async {
|
||
repo.initial = '2';
|
||
bool? result;
|
||
await openDialog(tester, cubit, (value) => result = value);
|
||
expect(find.text('控制模式确认'), findsOneWidget);
|
||
expect(find.textContaining('当前实时模式:自主任务'), findsOneWidget);
|
||
expect(repo.requests, isEmpty);
|
||
expect(result, isNull);
|
||
await tester.tap(find.text('确定'));
|
||
await tester.pumpAndSettle();
|
||
expect(result, isTrue);
|
||
expect(repo.requests, isEmpty);
|
||
expect(cubit.isClosed, isTrue);
|
||
});
|
||
|
||
testWidgets('确定才下发,接口成功立即关闭,不等模式回推', (tester) async {
|
||
repo.initial = '3';
|
||
repo.pending = Completer();
|
||
bool? result;
|
||
await openDialog(tester, cubit, (value) => result = value);
|
||
expect(repo.requests, isEmpty);
|
||
await tester.tap(find.text('确定'));
|
||
await tester.pump();
|
||
expect(repo.requests.single, ('SN002', ControlModeTarget.autonomous));
|
||
expect(result, isNull);
|
||
repo.pending!.complete(const Right(unit));
|
||
await tester.pumpAndSettle();
|
||
expect(result, isTrue);
|
||
expect(cubit.state.reading!.value, 3);
|
||
});
|
||
|
||
testWidgets('返回不会下发切换,关闭后后续实时数据不会触发进入', (tester) async {
|
||
repo.initial = '1';
|
||
bool? result;
|
||
await openDialog(tester, cubit, (value) => result = value);
|
||
await tester.tap(find.text('返回'));
|
||
await tester.pumpAndSettle();
|
||
repo.report('2');
|
||
await tester.pump();
|
||
expect(result, isFalse);
|
||
expect(repo.requests, isEmpty);
|
||
expect(cubit.isClosed, isTrue);
|
||
});
|
||
|
||
testWidgets('无模式数据不转圈且确定可点击,成功后直接进入', (tester) async {
|
||
bool? result;
|
||
await openDialog(tester, cubit, (value) => result = value);
|
||
expect(find.byType(LinearProgressIndicator), findsNothing);
|
||
expect(find.textContaining('暂未收到当前模式'), findsOneWidget);
|
||
expect(
|
||
tester.widget<FilledButton>(find.byType(FilledButton)).onPressed,
|
||
isNotNull,
|
||
);
|
||
await tester.tap(find.text('确定'));
|
||
await tester.pumpAndSettle();
|
||
expect(result, isTrue);
|
||
expect(repo.requests.single, ('SN002', ControlModeTarget.autonomous));
|
||
});
|
||
|
||
testWidgets('英文小横屏可以滚动查看内容且无布局溢出', (tester) async {
|
||
repo.initial = '1';
|
||
await openDialog(
|
||
tester,
|
||
cubit,
|
||
(_) {},
|
||
locale: const Locale('en', 'US'),
|
||
size: const Size(560, 280),
|
||
);
|
||
expect(find.text('Confirm control mode'), findsOneWidget);
|
||
expect(tester.takeException(), isNull);
|
||
await tester.tap(find.text('Back'));
|
||
await tester.pumpAndSettle();
|
||
expect(tester.takeException(), isNull);
|
||
});
|
||
|
||
testWidgets('遥控入口只在模式确认后激活一次,确认前页面动作被阻止', (tester) async {
|
||
repo.initial = '2';
|
||
repo.pending = Completer();
|
||
sl.registerSingleton(WatchDeviceControlModeUseCase(repo));
|
||
sl.registerSingleton(ChangeDeviceControlModeUseCase(repo));
|
||
var activations = 0;
|
||
var drives = 0;
|
||
await tester.pumpWidget(
|
||
app(
|
||
ControlModeEntryGate(
|
||
deviceId: 'SN002',
|
||
target: ControlModeTarget.remote,
|
||
onReady: () => activations++,
|
||
child: Scaffold(
|
||
body: TextButton(
|
||
key: const Key('drive'),
|
||
onPressed: () => drives++,
|
||
child: const Text('drive'),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
await tester.pumpAndSettle();
|
||
expect(activations, 0);
|
||
await tester.tap(find.byKey(const Key('drive')), warnIfMissed: false);
|
||
expect(drives, 0);
|
||
await tester.tap(find.text('确定'));
|
||
await tester.pump();
|
||
expect(repo.requests.single, ('SN002', ControlModeTarget.remote));
|
||
expect(activations, 0);
|
||
repo.pending!.complete(const Right(unit));
|
||
await tester.pumpAndSettle();
|
||
expect(activations, 1);
|
||
expect(drives, 0);
|
||
await tester.tap(find.byKey(const Key('drive')));
|
||
expect(drives, 1);
|
||
});
|
||
|
||
testWidgets('取消遥控模式确认返回来源页,不激活控制或下发切换', (tester) async {
|
||
repo.initial = '2';
|
||
sl.registerSingleton(WatchDeviceControlModeUseCase(repo));
|
||
sl.registerSingleton(ChangeDeviceControlModeUseCase(repo));
|
||
var activations = 0;
|
||
await tester.pumpWidget(
|
||
app(
|
||
Builder(
|
||
builder: (context) => Scaffold(
|
||
body: TextButton(
|
||
onPressed: () => Navigator.of(context).push(
|
||
MaterialPageRoute(
|
||
builder: (_) => ControlModeEntryGate(
|
||
deviceId: 'SN002',
|
||
target: ControlModeTarget.remote,
|
||
onReady: () => activations++,
|
||
child: const Scaffold(body: Text('remote')),
|
||
),
|
||
),
|
||
),
|
||
child: const Text('open remote'),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
await tester.pumpAndSettle();
|
||
await tester.tap(find.text('open remote'));
|
||
await tester.pumpAndSettle();
|
||
await tester.tap(find.text('返回'));
|
||
await tester.pumpAndSettle();
|
||
expect(find.text('open remote'), findsOneWidget);
|
||
expect(find.text('remote'), findsNothing);
|
||
expect(activations, 0);
|
||
expect(repo.requests, isEmpty);
|
||
});
|
||
}
|