import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:qiangbao_accounting/api/ai_api.dart'; import 'package:qiangbao_accounting/models/ai_config.dart'; import 'package:qiangbao_accounting/models/ai_persona.dart'; import 'package:qiangbao_accounting/pages/assistant/ai_config_page.dart'; import 'package:qiangbao_accounting/providers/ai_persona_provider.dart'; /// 「AI 设置页只管理人设、不负责选人」的回归测试。 /// /// 背景:一开始设置页做成「点一条就切换当前人设」,但这和数据模型是冲突的 —— /// 人设是**跟着会话走**的(新建对话时定,之后不改),设置页里再放一个「选人」 /// 就会让用户以为「在这儿选了就生效」,实际聊起来还是会话里那个。 /// /// 所以这里守两件事: /// 1. 设置页上没有任何表示「当前选中」的控件(单选圈),也不能通过点击切换 /// 2. 会话的人设不受「预选偏好」影响(这个是核心不变量,另有专门测试) class _FakeAiApi extends AiApi { _FakeAiApi() : super(Dio()); @override Future> configs() async => const []; @override Future> personas() async => const [ AiPersonaItem( id: null, name: '默认人设', systemPrompt: '内置的', promptLength: 3, isDefault: true, editable: false, defaultPrompt: '内置的', ), AiPersonaItem( id: 7, name: '花花', systemPrompt: '你是花花', promptLength: 4, isDefault: false, editable: true, defaultPrompt: null, ), AiPersonaItem( id: 8, name: '简洁模式', systemPrompt: '只回一句', promptLength: 4, isDefault: false, editable: true, defaultPrompt: null, ), ]; } void main() { TestWidgetsFlutterBinding.ensureInitialized(); SharedPreferences.setMockInitialValues({}); late _FakeAiApi api; setUp(() => api = _FakeAiApi()); Widget wrap() => ProviderScope( overrides: [aiApiProvider.overrideWithValue(api)], child: const MaterialApp(home: AiConfigPage()), ); Future settle(WidgetTester tester) async { await tester.pump(); await tester.pump(const Duration(milliseconds: 300)); } Future goToPersonaTab(WidgetTester tester) async { await tester.tap(find.text('人设')); await tester.pumpAndSettle(const Duration(milliseconds: 100)); } group('★ AI 设置页不负责选人', () { testWidgets('人设列表里没有任何单选圈(不给「当前选中」的错觉)', (tester) async { await tester.pumpWidget(wrap()); await settle(tester); await goToPersonaTab(tester); // 三条人设都该在 expect(find.text('默认人设'), findsOneWidget); expect(find.text('花花'), findsOneWidget); expect(find.text('简洁模式'), findsOneWidget); // 选中的两种圈都不该出现 expect(find.byIcon(Icons.radio_button_checked), findsNothing, reason: '设置页不该有「已选中」的视觉标记'); expect(find.byIcon(Icons.radio_button_unchecked), findsNothing, reason: '设置页不该有可选的单选圈'); }); testWidgets('★ 点人设卡片不会改变「预选偏好」', (tester) async { await tester.pumpWidget(wrap()); await settle(tester); await goToPersonaTab(tester); final container = ProviderScope.containerOf( tester.element(find.text('花花')), ); expect(container.read(preferredPersonaProvider), isNull); // 点「花花」整行 await tester.tap(find.text('花花')); await settle(tester); // 偏好不该被改 —— 设置页没有「选用」这个动作 expect(container.read(preferredPersonaProvider), isNull, reason: '点人设卡片不该把它变成「当前使用的人设」'); }); testWidgets('标题和说明写明「这里只管管理,选人是在新建对话时」', (tester) async { await tester.pumpWidget(wrap()); await settle(tester); await goToPersonaTab(tester); expect(find.text('管理人设'), findsOneWidget); expect(find.textContaining('用哪个是在新建对话时选的', findRichText: true), findsOneWidget, reason: '要明确告诉用户「在哪儿选人」,否则会以为这里能选'); }); testWidgets('仍然能编辑和删除(管理功能不受影响)', (tester) async { await tester.pumpWidget(wrap()); await settle(tester); await goToPersonaTab(tester); // 两条用户人设各有一个「更多」菜单;默认人设没有 expect(find.byIcon(Icons.more_vert), findsNWidgets(2), reason: '可编辑的人设才能有菜单,默认项不给'); }); }); }