整个课程和数据UI重新设计版本
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/testing.dart';
|
||||
import 'package:kouyu_english/core/ai_service.dart';
|
||||
import 'package:kouyu_english/core/app_state.dart';
|
||||
import 'package:kouyu_english/core/models.dart';
|
||||
import 'package:kouyu_english/features/dialogue/dialogue_flow.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('sending a turn after an AI correction counts as assisted', (
|
||||
tester,
|
||||
) async {
|
||||
// The page wires up recording and playback plugins that tests lack.
|
||||
final messenger = tester.binding.defaultBinaryMessenger;
|
||||
for (final name in const [
|
||||
'com.llfbandit.record/messages',
|
||||
'xyz.luan/audioplayers',
|
||||
'xyz.luan/audioplayers.global',
|
||||
]) {
|
||||
messenger.setMockMethodCallHandler(
|
||||
MethodChannel(name),
|
||||
(_) async => null,
|
||||
);
|
||||
}
|
||||
messenger.setMockStreamHandler(
|
||||
const EventChannel('xyz.luan/audioplayers.global/events'),
|
||||
MockStreamHandler.inline(onListen: (_, _) {}),
|
||||
);
|
||||
// Each player also opens its own event channel under a random id.
|
||||
final reportError = FlutterError.onError;
|
||||
FlutterError.onError = (details) {
|
||||
if (details.exception is! MissingPluginException) {
|
||||
reportError?.call(details);
|
||||
}
|
||||
};
|
||||
addTearDown(() => FlutterError.onError = reportError);
|
||||
AiService.instance.setFallbackApiKey('test-key');
|
||||
final state = AppState()
|
||||
..aiProvider = AiProviderType.compatible
|
||||
..aiEndpoint = 'https://api.deepseek.com'
|
||||
..aiModel = 'deepseek-flash';
|
||||
// A free scene only records items the learner has already been taught.
|
||||
state.mastery['A0-P01'] = MasteryItem(
|
||||
id: 'A0-P01',
|
||||
label: '介绍名字',
|
||||
status: MasteryStatus.recognize,
|
||||
evidence: const [],
|
||||
firstTaughtAt: DateTime.now().subtract(const Duration(days: 1)),
|
||||
);
|
||||
final check = jsonEncode({
|
||||
'schemaVersion': 'writing-feedback-1',
|
||||
'verdict': 'rewrite',
|
||||
'feedback': '介绍姓名正确,后半句有语法错误。',
|
||||
'suggestion': 'My name is Alex and I am happy.',
|
||||
'missing': ['I is 应为 I am'],
|
||||
'lessonId': 'dialogue-0',
|
||||
});
|
||||
final prompts = <String>[];
|
||||
http.Response reply(String content) => http.Response(
|
||||
jsonEncode({
|
||||
'choices': [
|
||||
{
|
||||
'message': {'content': content},
|
||||
},
|
||||
],
|
||||
}),
|
||||
200,
|
||||
headers: {'content-type': 'application/json; charset=utf-8'},
|
||||
);
|
||||
|
||||
await http.runWithClient(
|
||||
() async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: DialoguePage(state: state, onFinished: (_) {}),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.enterText(
|
||||
find.byType(TextField),
|
||||
'My name is Alex and I is happy',
|
||||
);
|
||||
await tester.pump();
|
||||
final aiButton = find.text('发送前 AI 检查语法和拼写(可选)');
|
||||
await tester.ensureVisible(aiButton);
|
||||
await tester.tap(aiButton);
|
||||
await tester.runAsync(
|
||||
() => Future<void>.delayed(const Duration(milliseconds: 50)),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('· I is 应为 I am'), findsOneWidget);
|
||||
expect(
|
||||
find.text('参考改正:My name is Alex and I am happy.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.runAsync(
|
||||
() => Future<void>.delayed(const Duration(milliseconds: 50)),
|
||||
);
|
||||
await tester.pump();
|
||||
},
|
||||
() => MockClient((request) async {
|
||||
final body = request.body;
|
||||
prompts.add(body);
|
||||
// The dialogue reply itself may fall back to the script.
|
||||
return reply(body.contains('writing-feedback-1') ? check : '{}');
|
||||
}),
|
||||
);
|
||||
|
||||
expect(prompts.first, contains('I is happy'));
|
||||
expect(prompts.first, contains('dialogue-0'));
|
||||
final attempt = state.attemptEvidence.singleWhere(
|
||||
(entry) => entry.taskId.startsWith('dialogue-scene-'),
|
||||
);
|
||||
expect(attempt.itemId, 'A0-P01');
|
||||
expect(attempt.outcome, EvidenceKind.assisted);
|
||||
expect(state.sceneDialogueDraft?.usedHelp, isTrue);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user