mirror of
https://github.com/foss42/apidash.git
synced 2025-12-05 12:34:26 +08:00
319 lines
9.0 KiB
Dart
319 lines
9.0 KiB
Dart
import 'package:apidash/dashbot/core/constants/constants.dart';
|
|
import 'package:apidash/dashbot/features/chat/models/chat_message.dart';
|
|
import 'package:apidash/dashbot/features/chat/models/chat_state.dart';
|
|
import 'package:apidash/dashbot/features/chat/view/pages/dashbot_chat_page.dart';
|
|
import 'package:apidash/dashbot/features/chat/view/widgets/dashbot_task_buttons.dart';
|
|
import 'package:apidash/dashbot/features/chat/viewmodel/chat_viewmodel.dart';
|
|
import 'package:apidash/providers/collection_providers.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'test_utils.dart';
|
|
|
|
void main() {
|
|
Widget createChatScreen({
|
|
List<Override> overrides = const [],
|
|
ChatMessageType? initialTask,
|
|
}) {
|
|
return ProviderScope(
|
|
overrides: [
|
|
// Override the selectedRequestModelProvider to prevent Hive dependency issues
|
|
selectedRequestModelProvider.overrideWith((ref) => null),
|
|
...overrides,
|
|
],
|
|
child: MaterialApp(
|
|
home: Scaffold(
|
|
body: ChatScreen(initialTask: initialTask),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
testWidgets('ChatScreen shows empty-state prompt when idle', (tester) async {
|
|
late SpyChatViewmodel spy;
|
|
await tester.pumpWidget(
|
|
createChatScreen(
|
|
overrides: [
|
|
chatViewmodelProvider.overrideWith((ref) {
|
|
spy = SpyChatViewmodel(ref);
|
|
spy.setState(const ChatState());
|
|
return spy;
|
|
}),
|
|
],
|
|
),
|
|
);
|
|
await tester.pump();
|
|
|
|
expect(find.text('Ask me anything!'), findsOneWidget);
|
|
expect(spy.sendMessageCalls, isEmpty);
|
|
});
|
|
|
|
testWidgets('ChatScreen triggers initial task without user input',
|
|
(tester) async {
|
|
late SpyChatViewmodel spy;
|
|
await tester.pumpWidget(
|
|
createChatScreen(
|
|
initialTask: ChatMessageType.generateDoc,
|
|
overrides: [
|
|
chatViewmodelProvider.overrideWith((ref) {
|
|
spy = SpyChatViewmodel(ref);
|
|
spy.setState(const ChatState());
|
|
return spy;
|
|
}),
|
|
],
|
|
),
|
|
);
|
|
|
|
await tester.pump();
|
|
|
|
expect(spy.sendMessageCalls.length, 1);
|
|
expect(spy.sendMessageCalls.first.text, isEmpty);
|
|
expect(spy.sendMessageCalls.first.type, ChatMessageType.generateDoc);
|
|
expect(spy.sendMessageCalls.first.countAsUser, isFalse);
|
|
});
|
|
|
|
testWidgets('ChatScreen toggles task suggestions panel', (tester) async {
|
|
late SpyChatViewmodel spy;
|
|
await tester.pumpWidget(
|
|
createChatScreen(
|
|
overrides: [
|
|
chatViewmodelProvider.overrideWith((ref) {
|
|
spy = SpyChatViewmodel(ref);
|
|
spy.setState(const ChatState());
|
|
return spy;
|
|
}),
|
|
],
|
|
),
|
|
);
|
|
|
|
expect(find.byType(DashbotTaskButtons), findsNothing);
|
|
|
|
await tester.tap(find.byIcon(Icons.help_outline_rounded));
|
|
await tester.pump();
|
|
|
|
expect(find.byType(DashbotTaskButtons), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Clear chat icon delegates to viewmodel', (tester) async {
|
|
late SpyChatViewmodel spy;
|
|
await tester.pumpWidget(
|
|
createChatScreen(
|
|
overrides: [
|
|
chatViewmodelProvider.overrideWith((ref) {
|
|
spy = SpyChatViewmodel(ref);
|
|
spy.setState(const ChatState());
|
|
return spy;
|
|
}),
|
|
],
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.byIcon(Icons.clear_all_rounded));
|
|
await tester.pump();
|
|
|
|
expect(spy.clearCalled, isTrue);
|
|
});
|
|
|
|
testWidgets('Submitting text sends general chat message', (tester) async {
|
|
late SpyChatViewmodel spy;
|
|
await tester.pumpWidget(
|
|
createChatScreen(
|
|
overrides: [
|
|
chatViewmodelProvider.overrideWith((ref) {
|
|
spy = SpyChatViewmodel(ref);
|
|
spy.setState(const ChatState());
|
|
return spy;
|
|
}),
|
|
],
|
|
),
|
|
);
|
|
|
|
await tester.enterText(find.byType(TextField), 'Hello Dashbot');
|
|
await tester.tap(find.byIcon(Icons.send_rounded));
|
|
await tester.pump();
|
|
|
|
expect(spy.sendMessageCalls.length, 1);
|
|
expect(spy.sendMessageCalls.first.text, 'Hello Dashbot');
|
|
expect(spy.sendMessageCalls.first.type, ChatMessageType.general);
|
|
});
|
|
|
|
testWidgets('Streaming state renders temporary ChatBubble', (tester) async {
|
|
late SpyChatViewmodel spy;
|
|
await tester.pumpWidget(
|
|
createChatScreen(
|
|
overrides: [
|
|
chatViewmodelProvider.overrideWith((ref) {
|
|
spy = SpyChatViewmodel(ref);
|
|
spy.setState(const ChatState(
|
|
isGenerating: true, currentStreamingResponse: 'Streaming...'));
|
|
return spy;
|
|
}),
|
|
],
|
|
),
|
|
);
|
|
|
|
await tester.pump();
|
|
|
|
final markdown =
|
|
tester.widget<MarkdownBody>(find.byType(MarkdownBody).first);
|
|
expect(markdown.data, 'Streaming...');
|
|
});
|
|
|
|
testWidgets('Existing chat messages render in list', (tester) async {
|
|
late SpyChatViewmodel spy;
|
|
final messages = [
|
|
ChatMessage(
|
|
id: '1',
|
|
content: 'First',
|
|
role: MessageRole.user,
|
|
timestamp: DateTime(2024),
|
|
),
|
|
ChatMessage(
|
|
id: '2',
|
|
content: 'Second',
|
|
role: MessageRole.system,
|
|
timestamp: DateTime(2024, 2),
|
|
),
|
|
];
|
|
|
|
await tester.pumpWidget(
|
|
createChatScreen(
|
|
overrides: [
|
|
chatViewmodelProvider.overrideWith((ref) {
|
|
spy = SpyChatViewmodel(ref);
|
|
spy.setMessages(messages);
|
|
spy.setState(ChatState(chatSessions: {'global': messages}));
|
|
return spy;
|
|
}),
|
|
],
|
|
),
|
|
);
|
|
|
|
await tester.pump();
|
|
|
|
expect(find.byType(ListView), findsOneWidget);
|
|
expect(find.text('First'), findsOneWidget);
|
|
expect(find.text('Second'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('TextField onSubmitted sends message', (tester) async {
|
|
late SpyChatViewmodel spy;
|
|
await tester.pumpWidget(
|
|
createChatScreen(
|
|
overrides: [
|
|
chatViewmodelProvider.overrideWith((ref) {
|
|
spy = SpyChatViewmodel(ref);
|
|
spy.setState(const ChatState());
|
|
return spy;
|
|
}),
|
|
],
|
|
),
|
|
);
|
|
|
|
await tester.enterText(find.byType(TextField), 'Test message');
|
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
await tester.pump();
|
|
|
|
expect(spy.sendMessageCalls.length, 1);
|
|
expect(spy.sendMessageCalls.first.text, 'Test message');
|
|
expect(spy.sendMessageCalls.first.type, ChatMessageType.general);
|
|
});
|
|
|
|
testWidgets('Task suggestions panel hides when generating starts',
|
|
(tester) async {
|
|
late SpyChatViewmodel spy;
|
|
await tester.pumpWidget(
|
|
createChatScreen(
|
|
overrides: [
|
|
chatViewmodelProvider.overrideWith((ref) {
|
|
spy = SpyChatViewmodel(ref);
|
|
spy.setState(const ChatState());
|
|
return spy;
|
|
}),
|
|
],
|
|
),
|
|
);
|
|
|
|
// First show task suggestions
|
|
await tester.tap(find.byIcon(Icons.help_outline_rounded));
|
|
await tester.pump();
|
|
expect(find.byType(DashbotTaskButtons), findsOneWidget);
|
|
|
|
// Then start generating - this should hide the task suggestions
|
|
spy.setState(const ChatState(isGenerating: true));
|
|
await tester.pump();
|
|
|
|
expect(find.byType(DashbotTaskButtons), findsNothing);
|
|
});
|
|
|
|
testWidgets('Scroll animation triggers on streaming response changes',
|
|
(tester) async {
|
|
late SpyChatViewmodel spy;
|
|
await tester.pumpWidget(
|
|
createChatScreen(
|
|
overrides: [
|
|
chatViewmodelProvider.overrideWith((ref) {
|
|
spy = SpyChatViewmodel(ref);
|
|
spy.setState(const ChatState(
|
|
isGenerating: true,
|
|
currentStreamingResponse: 'Initial...',
|
|
));
|
|
return spy;
|
|
}),
|
|
],
|
|
),
|
|
);
|
|
|
|
await tester.pump();
|
|
|
|
// Change the streaming response - this should trigger scroll
|
|
spy.setState(const ChatState(
|
|
isGenerating: true,
|
|
currentStreamingResponse: 'Updated streaming response...',
|
|
));
|
|
await tester.pump();
|
|
|
|
// Verify scrolling behavior by checking that the new content is rendered
|
|
expect(find.text('Updated streaming response...'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Scroll animation triggers when generation completes',
|
|
(tester) async {
|
|
late SpyChatViewmodel spy;
|
|
final messages = [
|
|
ChatMessage(
|
|
id: '1',
|
|
content: 'Generated response',
|
|
role: MessageRole.system,
|
|
timestamp: DateTime(2024),
|
|
),
|
|
];
|
|
|
|
await tester.pumpWidget(
|
|
createChatScreen(
|
|
overrides: [
|
|
chatViewmodelProvider.overrideWith((ref) {
|
|
spy = SpyChatViewmodel(ref);
|
|
spy.setState(const ChatState(isGenerating: true));
|
|
return spy;
|
|
}),
|
|
],
|
|
),
|
|
);
|
|
|
|
await tester.pump();
|
|
|
|
// Complete generation - this should trigger scroll
|
|
spy.setMessages(messages);
|
|
spy.setState(ChatState(
|
|
isGenerating: false,
|
|
chatSessions: {'global': messages},
|
|
));
|
|
await tester.pump();
|
|
|
|
expect(find.text('Generated response'), findsOneWidget);
|
|
});
|
|
}
|