151 lines
5.3 KiB
Dart
151 lines
5.3 KiB
Dart
@TestOn('vm')
|
|
library;
|
|
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:xworkmate/app/app_controller.dart';
|
|
import 'package:xworkmate/features/settings/settings_page.dart';
|
|
import 'package:xworkmate/runtime/secure_config_store.dart';
|
|
|
|
import '../test_support.dart';
|
|
|
|
void main() {
|
|
testWidgets(
|
|
'SettingsPage AI Gateway draft/save/apply flow persists edited fields through the global actions',
|
|
(WidgetTester tester) async {
|
|
late _AiGatewaySettingsTestController controller;
|
|
await tester.runAsync(() async {
|
|
SharedPreferences.setMockInitialValues(<String, Object>{});
|
|
final testRoot =
|
|
'${Directory.systemTemp.path}/xworkmate-widget-tests-${DateTime.now().microsecondsSinceEpoch}';
|
|
controller = _AiGatewaySettingsTestController(
|
|
store: SecureConfigStore(
|
|
enableSecureStorage: false,
|
|
databasePathResolver: () async => '$testRoot/settings.sqlite3',
|
|
fallbackDirectoryPathResolver: () async => testRoot,
|
|
),
|
|
);
|
|
await _waitFor(() => !controller.initializing);
|
|
});
|
|
addTearDown(controller.dispose);
|
|
|
|
final staleGateway = controller.settings.aiGateway.copyWith(
|
|
name: 'default',
|
|
baseUrl: '',
|
|
apiKeyRef: 'ai_gateway_api_key',
|
|
availableModels: const <String>['stale-model'],
|
|
selectedModels: const <String>['stale-model'],
|
|
syncState: 'invalid',
|
|
syncMessage: 'Missing AI Gateway URL',
|
|
);
|
|
await tester.runAsync(() async {
|
|
await controller.saveSettings(
|
|
controller.settings.copyWith(
|
|
aiGateway: staleGateway,
|
|
multiAgent: controller.settings.multiAgent.copyWith(
|
|
autoSync: false,
|
|
),
|
|
),
|
|
refreshAfterSave: false,
|
|
);
|
|
});
|
|
|
|
await pumpPage(
|
|
tester,
|
|
child: SettingsPage(controller: controller),
|
|
);
|
|
|
|
await tester.tap(find.text('集成'));
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
|
|
await tester.enterText(
|
|
find.byKey(const ValueKey('ai-gateway-name-field')),
|
|
'default',
|
|
);
|
|
await tester.enterText(
|
|
find.byKey(const ValueKey('ai-gateway-url-field')),
|
|
'https://api.svc.plus/v1',
|
|
);
|
|
await tester.enterText(
|
|
find.byKey(const ValueKey('ai-gateway-api-key-ref-field')),
|
|
'ai_gateway_api_key',
|
|
);
|
|
|
|
expect(
|
|
tester
|
|
.widget<TextField>(
|
|
find.byKey(const ValueKey('ai-gateway-url-field')),
|
|
)
|
|
.controller!
|
|
.text,
|
|
'https://api.svc.plus/v1',
|
|
);
|
|
expect(find.byKey(const ValueKey('ai-gateway-save-button')), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('ai-gateway-apply-button')), findsOneWidget);
|
|
expect(
|
|
find.byKey(const ValueKey('settings-global-save-button')),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.byKey(const ValueKey('settings-global-apply-button')),
|
|
findsOneWidget,
|
|
);
|
|
|
|
expect(controller.settingsDraft.aiGateway.baseUrl, 'https://api.svc.plus/v1');
|
|
expect(controller.settings.aiGateway.baseUrl, isEmpty);
|
|
|
|
final saveButton = tester.widget<OutlinedButton>(
|
|
find.byKey(const ValueKey('ai-gateway-save-button')),
|
|
);
|
|
await tester.runAsync(() async {
|
|
saveButton.onPressed!.call();
|
|
await _waitFor(() => controller.hasPendingSettingsApply);
|
|
});
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
|
|
expect(controller.hasPendingSettingsApply, isTrue);
|
|
expect(controller.settings.aiGateway.baseUrl, 'https://api.svc.plus/v1');
|
|
|
|
final applyButton = tester.widget<FilledButton>(
|
|
find.byKey(const ValueKey('ai-gateway-apply-button')),
|
|
);
|
|
await tester.runAsync(() async {
|
|
applyButton.onPressed!.call();
|
|
await _waitFor(() => !controller.hasPendingSettingsApply);
|
|
});
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
|
|
expect(controller.settings.aiGateway.name, 'default');
|
|
expect(controller.settings.aiGateway.baseUrl, 'https://api.svc.plus/v1');
|
|
expect(controller.settings.aiGateway.apiKeyRef, 'ai_gateway_api_key');
|
|
expect(controller.settings.aiGateway.availableModels, isEmpty);
|
|
expect(controller.settings.aiGateway.selectedModels, isEmpty);
|
|
expect(controller.settings.aiGateway.syncState, 'idle');
|
|
expect(controller.settings.aiGateway.syncMessage, 'Ready to sync models');
|
|
expect(controller.hasPendingSettingsApply, isFalse);
|
|
expect(find.text('Missing AI Gateway URL'), findsNothing);
|
|
expect(find.text('Ready to sync models'), findsOneWidget);
|
|
},
|
|
);
|
|
}
|
|
|
|
class _AiGatewaySettingsTestController extends AppController {
|
|
_AiGatewaySettingsTestController({super.store});
|
|
|
|
@override
|
|
Future<void> refreshMultiAgentMounts({bool sync = false}) async {}
|
|
}
|
|
|
|
Future<void> _waitFor(bool Function() predicate) async {
|
|
final deadline = DateTime.now().add(const Duration(seconds: 10));
|
|
while (!predicate()) {
|
|
if (DateTime.now().isAfter(deadline)) {
|
|
throw StateError('condition not met before timeout');
|
|
}
|
|
await Future<void>.delayed(const Duration(milliseconds: 20));
|
|
}
|
|
}
|