xworkmate-app/test/features/mobile/ios_mobile_shell_test.dart

231 lines
6.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:xworkmate/app/app_shell.dart';
import 'package:xworkmate/app/ui_feature_manifest.dart';
import 'package:xworkmate/features/mobile/mobile_shell.dart';
import 'package:xworkmate/models/app_models.dart';
import 'package:xworkmate/widgets/detail_drawer.dart';
import 'package:xworkmate/theme/app_theme.dart';
import '../../test_support.dart';
void main() {
Future<void> pumpMobileShell(
WidgetTester tester, {
required Widget child,
required TargetPlatform platform,
}) async {
tester.view.devicePixelRatio = 1;
tester.view.physicalSize = const Size(430, 1200);
addTearDown(() {
tester.view.resetPhysicalSize();
tester.view.resetDevicePixelRatio();
});
await tester.pumpWidget(
MaterialApp(
locale: const Locale('zh'),
supportedLocales: const [Locale('zh'), Locale('en')],
localizationsDelegates: GlobalMaterialLocalizations.delegates,
theme: AppTheme.light(platform: platform),
darkTheme: AppTheme.dark(platform: platform),
home: child,
),
);
await tester.pumpAndSettle();
}
for (final platform in <TargetPlatform>[
TargetPlatform.iOS,
TargetPlatform.android,
]) {
testWidgets(
'MobileShell saves local account entry from the workspace account page on $platform',
(WidgetTester tester) async {
final controller = await createTestController(tester);
await pumpMobileShell(
tester,
child: MobileShell(controller: controller),
platform: platform,
);
await tester.tap(find.text('工作区'));
await tester.pumpAndSettle();
final accountEntry = find.text('账号').first;
await tester.ensureVisible(accountEntry);
await tester.pumpAndSettle();
await tester.tap(accountEntry);
await tester.pumpAndSettle();
await tester.enterText(
find.byKey(const ValueKey('account-base-url-field')),
'https://accounts.qa.example',
);
await tester.enterText(
find.byKey(const ValueKey('account-username-field')),
'qa@example.com',
);
await tester.pumpAndSettle();
final saveButton = find.widgetWithText(FilledButton, '保存本地入口');
await tester.ensureVisible(saveButton);
await tester.pumpAndSettle();
await tester.tap(saveButton);
await tester.pump(const Duration(milliseconds: 300));
await tester.pumpAndSettle();
expect(
controller.settings.accountBaseUrl,
'https://accounts.qa.example',
);
expect(controller.settings.accountUsername, 'qa@example.com');
},
);
}
testWidgets('MobileShell workspace launcher routes into module pages', (
WidgetTester tester,
) async {
final controller = await createTestController(tester);
await pumpMobileShell(
tester,
child: MobileShell(controller: controller),
platform: TargetPlatform.android,
);
await tester.tap(find.text('工作区'));
await tester.pumpAndSettle();
expect(find.text('MCP Hub'), findsOneWidget);
await tester.tap(find.text('节点').first);
await tester.pumpAndSettle();
expect(controller.destination, WorkspaceDestination.nodes);
expect(find.text('模块'), findsWidgets);
expect(tester.takeException(), isNull);
});
testWidgets('MobileShell workspace launcher filters disabled entries', (
WidgetTester tester,
) async {
final manifest = UiFeatureManifest.fallback().copyWithFeature(
platform: UiFeaturePlatform.mobile,
module: 'workspace',
feature: 'mcp_server',
enabled: false,
);
final controller = await createTestController(
tester,
uiFeatureManifest: manifest,
);
await pumpMobileShell(
tester,
child: MobileShell(controller: controller),
platform: TargetPlatform.android,
);
await tester.tap(find.text('工作区'));
await tester.pumpAndSettle();
expect(find.text('MCP Hub'), findsNothing);
expect(find.text('节点'), findsOneWidget);
});
testWidgets('MobileShell renders detail panels as bottom sheets', (
WidgetTester tester,
) async {
final controller = await createTestController(tester);
await pumpMobileShell(
tester,
child: MobileShell(controller: controller),
platform: TargetPlatform.android,
);
controller.openDetail(
DetailPanelData(
title: 'Test Detail',
subtitle: 'Mobile',
icon: Icons.extension_rounded,
status: const StatusInfo('Ready', StatusTone.success),
description: 'Detail content',
meta: const <String>[],
sections: const <DetailSection>[],
actions: const <String>[],
),
);
await tester.pumpAndSettle();
expect(find.byType(DetailSheet), findsOneWidget);
});
testWidgets('AppShell uses MobileShell on compact iOS and Android only', (
WidgetTester tester,
) async {
final compactController = await createTestController(tester);
await pumpMobileShell(
tester,
child: AppShell(controller: compactController),
platform: TargetPlatform.android,
);
expect(find.byType(MobileShell), findsOneWidget);
final compactIosController = await createTestController(tester);
await pumpMobileShell(
tester,
child: AppShell(controller: compactIosController),
platform: TargetPlatform.iOS,
);
expect(find.byType(MobileShell), findsOneWidget);
final desktopAndroidController = await createTestController(tester);
tester.view.devicePixelRatio = 1;
tester.view.physicalSize = const Size(1200, 1000);
addTearDown(() {
tester.view.resetPhysicalSize();
tester.view.resetDevicePixelRatio();
});
await tester.pumpWidget(
MaterialApp(
locale: const Locale('zh'),
supportedLocales: const [Locale('zh'), Locale('en')],
localizationsDelegates: GlobalMaterialLocalizations.delegates,
theme: AppTheme.light(platform: TargetPlatform.android),
darkTheme: AppTheme.dark(platform: TargetPlatform.android),
home: AppShell(controller: desktopAndroidController),
),
);
await tester.pumpAndSettle();
expect(find.byType(MobileShell), findsNothing);
});
testWidgets('MobileShell exposes mobile-safe pairing shortcuts', (
WidgetTester tester,
) async {
final controller = await createTestController(tester);
await pumpMobileShell(
tester,
child: MobileShell(controller: controller),
platform: TargetPlatform.iOS,
);
expect(find.byKey(const ValueKey('mobile-safe-strip')), findsOneWidget);
expect(
find.byKey(const ValueKey('mobile-safe-open-button')),
findsOneWidget,
);
expect(
find.byKey(const ValueKey('mobile-safe-connect-button')),
findsOneWidget,
);
});
}