AI助手优化-人设与会话绑定

This commit is contained in:
2026-09-29 10:44:39 +08:00
parent f4ad2f8612
commit 900d48697e
5 changed files with 464 additions and 150 deletions
+141
View File
@@ -0,0 +1,141 @@
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<List<AiConfigItem>> configs() async => const [];
@override
Future<List<AiPersonaItem>> 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<void> settle(WidgetTester tester) async {
await tester.pump();
await tester.pump(const Duration(milliseconds: 300));
}
Future<void> 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: '可编辑的人设才能有菜单,默认项不给');
});
});
}