mirror of
https://github.com/foss42/apidash.git
synced 2025-12-15 10:18:20 +08:00
test: add test for all dashbot pages
This commit is contained in:
318
test/dashbot/pages/dashbot_chat_page_test.dart
Normal file
318
test/dashbot/pages/dashbot_chat_page_test.dart
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
128
test/dashbot/pages/dashbot_default_page_test.dart
Normal file
128
test/dashbot/pages/dashbot_default_page_test.dart
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import 'package:apidash/dashbot/core/common/pages/dashbot_default_page.dart';
|
||||||
|
import 'package:apidash/dashbot/core/constants/constants.dart';
|
||||||
|
import 'package:apidash/dashbot/core/routes/dashbot_routes.dart';
|
||||||
|
import 'package:apidash/dashbot/features/home/view/widgets/home_screen_task_button.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'test_utils.dart';
|
||||||
|
|
||||||
|
Finder _taskButton(String snippet) => find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is HomeScreenTaskButton && widget.label.contains(snippet),
|
||||||
|
);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('DashbotDefaultPage renders greeting and actions',
|
||||||
|
(tester) async {
|
||||||
|
await tester.pumpWidget(const MaterialApp(home: DashbotDefaultPage()));
|
||||||
|
|
||||||
|
expect(find.textContaining('Hello there'), findsOneWidget);
|
||||||
|
expect(find.textContaining('make one'), findsOneWidget);
|
||||||
|
expect(find.textContaining('Open Chat'), findsOneWidget);
|
||||||
|
expect(find.textContaining('Import cURL'), findsOneWidget);
|
||||||
|
expect(find.textContaining('Import OpenAPI'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Open Chat button pushes chat route without arguments',
|
||||||
|
(tester) async {
|
||||||
|
final observer = RecordingNavigatorObserver();
|
||||||
|
Object? capturedArgs;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
navigatorObservers: [observer],
|
||||||
|
onGenerateRoute: (settings) {
|
||||||
|
if (settings.name == DashbotRoutes.dashbotChat) {
|
||||||
|
capturedArgs = settings.arguments;
|
||||||
|
}
|
||||||
|
return MaterialPageRoute(
|
||||||
|
settings: settings,
|
||||||
|
builder: (_) => const SizedBox.shrink(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
home: const DashbotDefaultPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
final openChatButton = _taskButton('Open Chat');
|
||||||
|
expect(openChatButton, findsOneWidget);
|
||||||
|
await tester.tap(find.descendant(
|
||||||
|
of: openChatButton,
|
||||||
|
matching: find.byType(TextButton),
|
||||||
|
));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(observer.lastRoute?.settings.name, DashbotRoutes.dashbotChat);
|
||||||
|
expect(capturedArgs, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Import buttons push chat route with correct arguments', () {
|
||||||
|
testWidgets('Import cURL button', (tester) async {
|
||||||
|
final observer = RecordingNavigatorObserver();
|
||||||
|
Object? capturedArgs;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
navigatorObservers: [observer],
|
||||||
|
onGenerateRoute: (settings) {
|
||||||
|
if (settings.name == DashbotRoutes.dashbotChat) {
|
||||||
|
capturedArgs = settings.arguments;
|
||||||
|
}
|
||||||
|
return MaterialPageRoute(
|
||||||
|
settings: settings,
|
||||||
|
builder: (_) => const SizedBox.shrink(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
home: const DashbotDefaultPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
final importCurlButton = _taskButton('Import cURL');
|
||||||
|
expect(importCurlButton, findsOneWidget);
|
||||||
|
await tester.tap(find.descendant(
|
||||||
|
of: importCurlButton,
|
||||||
|
matching: find.byType(TextButton),
|
||||||
|
));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(observer.lastRoute?.settings.name, DashbotRoutes.dashbotChat);
|
||||||
|
expect(capturedArgs, ChatMessageType.importCurl);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Import OpenAPI button', (tester) async {
|
||||||
|
final observer = RecordingNavigatorObserver();
|
||||||
|
Object? capturedArgs;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
navigatorObservers: [observer],
|
||||||
|
onGenerateRoute: (settings) {
|
||||||
|
if (settings.name == DashbotRoutes.dashbotChat) {
|
||||||
|
capturedArgs = settings.arguments;
|
||||||
|
}
|
||||||
|
return MaterialPageRoute(
|
||||||
|
settings: settings,
|
||||||
|
builder: (_) => const SizedBox.shrink(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
home: const DashbotDefaultPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
final importOpenApiButton = _taskButton('Import OpenAPI');
|
||||||
|
expect(importOpenApiButton, findsOneWidget);
|
||||||
|
await tester.tap(find.descendant(
|
||||||
|
of: importOpenApiButton,
|
||||||
|
matching: find.byType(TextButton),
|
||||||
|
));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(observer.lastRoute?.settings.name, DashbotRoutes.dashbotChat);
|
||||||
|
expect(capturedArgs, ChatMessageType.importOpenApi);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
311
test/dashbot/pages/dashbot_home_page_test.dart
Normal file
311
test/dashbot/pages/dashbot_home_page_test.dart
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
import 'package:apidash/dashbot/features/home/view/pages/dashbot_home_page.dart';
|
||||||
|
import 'package:apidash/dashbot/features/home/view/widgets/home_screen_task_button.dart';
|
||||||
|
import 'package:apidash/dashbot/core/constants/constants.dart';
|
||||||
|
import 'package:apidash/dashbot/core/providers/dashbot_window_notifier.dart';
|
||||||
|
import 'package:apidash/dashbot/core/routes/dashbot_routes.dart';
|
||||||
|
import 'package:apidash/models/request_model.dart';
|
||||||
|
import 'package:apidash/providers/collection_providers.dart';
|
||||||
|
import 'package:apidash_core/apidash_core.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../widgets/action_buttons/test_utils.dart';
|
||||||
|
|
||||||
|
Finder _taskButton(String snippet) => find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is HomeScreenTaskButton && widget.label.contains(snippet),
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<RecordingDashbotWindowNotifier> _pumpHomePage(
|
||||||
|
WidgetTester tester, {
|
||||||
|
RequestModel? selectedModel,
|
||||||
|
void Function(String? name, Object? arguments)? onRoute,
|
||||||
|
}) async {
|
||||||
|
final windowNotifier = RecordingDashbotWindowNotifier();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
ProviderScope(
|
||||||
|
overrides: [
|
||||||
|
dashbotWindowNotifierProvider.overrideWith((ref) => windowNotifier),
|
||||||
|
selectedRequestModelProvider.overrideWith((ref) => selectedModel),
|
||||||
|
],
|
||||||
|
child: MaterialApp(
|
||||||
|
onGenerateRoute: (settings) {
|
||||||
|
onRoute?.call(settings.name, settings.arguments);
|
||||||
|
return MaterialPageRoute(
|
||||||
|
settings: settings,
|
||||||
|
builder: (_) => const SizedBox.shrink(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
home: const Scaffold(body: DashbotHomePage()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
return windowNotifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('DashbotHomePage renders greeting and quick actions',
|
||||||
|
(tester) async {
|
||||||
|
await _pumpHomePage(tester, onRoute: (_, __) {});
|
||||||
|
|
||||||
|
expect(find.textContaining('Hello there'), findsOneWidget);
|
||||||
|
expect(find.textContaining('How can I help you today'), findsOneWidget);
|
||||||
|
// Note: 'Chat with Dashbot' is only available in debug mode, so we don't test for it here
|
||||||
|
expect(find.textContaining('Explain me this response'), findsOneWidget);
|
||||||
|
expect(find.textContaining('Generate documentation'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Chat with Dashbot button appears and works in debug mode',
|
||||||
|
(tester) async {
|
||||||
|
// In debug mode (which tests run in), the button should be present
|
||||||
|
String? capturedRoute;
|
||||||
|
Object? capturedArgs;
|
||||||
|
|
||||||
|
await _pumpHomePage(
|
||||||
|
tester,
|
||||||
|
onRoute: (name, arguments) {
|
||||||
|
capturedRoute = name;
|
||||||
|
capturedArgs = arguments;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// The button should be visible in debug mode
|
||||||
|
final buttonFinder = _taskButton('Chat with Dashbot');
|
||||||
|
expect(buttonFinder, findsOneWidget);
|
||||||
|
|
||||||
|
// Tap the button
|
||||||
|
await tester.tap(find.descendant(
|
||||||
|
of: buttonFinder,
|
||||||
|
matching: find.byType(TextButton),
|
||||||
|
));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Should navigate to chat without any initial task arguments
|
||||||
|
expect(capturedRoute, DashbotRoutes.dashbotChat);
|
||||||
|
expect(capturedArgs, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Quick action buttons navigate with correct arguments', () {
|
||||||
|
testWidgets('Explain me this response button', (tester) async {
|
||||||
|
String? capturedRoute;
|
||||||
|
Object? capturedArgs;
|
||||||
|
|
||||||
|
await _pumpHomePage(
|
||||||
|
tester,
|
||||||
|
onRoute: (name, arguments) {
|
||||||
|
capturedRoute = name;
|
||||||
|
capturedArgs = arguments;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final buttonFinder = _taskButton('Explain me this response');
|
||||||
|
expect(buttonFinder, findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.descendant(
|
||||||
|
of: buttonFinder,
|
||||||
|
matching: find.byType(TextButton),
|
||||||
|
));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(capturedRoute, DashbotRoutes.dashbotChat);
|
||||||
|
expect(capturedArgs, ChatMessageType.explainResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Help me debug this error button', (tester) async {
|
||||||
|
String? capturedRoute;
|
||||||
|
Object? capturedArgs;
|
||||||
|
|
||||||
|
await _pumpHomePage(
|
||||||
|
tester,
|
||||||
|
onRoute: (name, arguments) {
|
||||||
|
capturedRoute = name;
|
||||||
|
capturedArgs = arguments;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final buttonFinder = _taskButton('Help me debug this error');
|
||||||
|
expect(buttonFinder, findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.descendant(
|
||||||
|
of: buttonFinder,
|
||||||
|
matching: find.byType(TextButton),
|
||||||
|
));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(capturedRoute, DashbotRoutes.dashbotChat);
|
||||||
|
expect(capturedArgs, ChatMessageType.debugError);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Generate documentation button', (tester) async {
|
||||||
|
String? capturedRoute;
|
||||||
|
Object? capturedArgs;
|
||||||
|
|
||||||
|
await _pumpHomePage(
|
||||||
|
tester,
|
||||||
|
onRoute: (name, arguments) {
|
||||||
|
capturedRoute = name;
|
||||||
|
capturedArgs = arguments;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final buttonFinder = _taskButton('Generate documentation');
|
||||||
|
expect(buttonFinder, findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.descendant(
|
||||||
|
of: buttonFinder,
|
||||||
|
matching: find.byType(TextButton),
|
||||||
|
));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(capturedRoute, DashbotRoutes.dashbotChat);
|
||||||
|
expect(capturedArgs, ChatMessageType.generateDoc);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Generate Tests button', (tester) async {
|
||||||
|
String? capturedRoute;
|
||||||
|
Object? capturedArgs;
|
||||||
|
|
||||||
|
await _pumpHomePage(
|
||||||
|
tester,
|
||||||
|
onRoute: (name, arguments) {
|
||||||
|
capturedRoute = name;
|
||||||
|
capturedArgs = arguments;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final buttonFinder = _taskButton('Generate Tests');
|
||||||
|
expect(buttonFinder, findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.descendant(
|
||||||
|
of: buttonFinder,
|
||||||
|
matching: find.byType(TextButton),
|
||||||
|
));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(capturedRoute, DashbotRoutes.dashbotChat);
|
||||||
|
expect(capturedArgs, ChatMessageType.generateTest);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Generate Code button', (tester) async {
|
||||||
|
String? capturedRoute;
|
||||||
|
Object? capturedArgs;
|
||||||
|
|
||||||
|
await _pumpHomePage(
|
||||||
|
tester,
|
||||||
|
onRoute: (name, arguments) {
|
||||||
|
capturedRoute = name;
|
||||||
|
capturedArgs = arguments;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final buttonFinder = _taskButton('Generate Code');
|
||||||
|
expect(buttonFinder, findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.descendant(
|
||||||
|
of: buttonFinder,
|
||||||
|
matching: find.byType(TextButton),
|
||||||
|
));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(capturedRoute, DashbotRoutes.dashbotChat);
|
||||||
|
expect(capturedArgs, ChatMessageType.generateCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Import cURL button', (tester) async {
|
||||||
|
String? capturedRoute;
|
||||||
|
Object? capturedArgs;
|
||||||
|
|
||||||
|
await _pumpHomePage(
|
||||||
|
tester,
|
||||||
|
onRoute: (name, arguments) {
|
||||||
|
capturedRoute = name;
|
||||||
|
capturedArgs = arguments;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final buttonFinder = _taskButton('Import cURL');
|
||||||
|
expect(buttonFinder, findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.descendant(
|
||||||
|
of: buttonFinder,
|
||||||
|
matching: find.byType(TextButton),
|
||||||
|
));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(capturedRoute, DashbotRoutes.dashbotChat);
|
||||||
|
expect(capturedArgs, ChatMessageType.importCurl);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Import OpenAPI button', (tester) async {
|
||||||
|
String? capturedRoute;
|
||||||
|
Object? capturedArgs;
|
||||||
|
|
||||||
|
await _pumpHomePage(
|
||||||
|
tester,
|
||||||
|
onRoute: (name, arguments) {
|
||||||
|
capturedRoute = name;
|
||||||
|
capturedArgs = arguments;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final buttonFinder = _taskButton('Import OpenAPI');
|
||||||
|
expect(buttonFinder, findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.descendant(
|
||||||
|
of: buttonFinder,
|
||||||
|
matching: find.byType(TextButton),
|
||||||
|
));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(capturedRoute, DashbotRoutes.dashbotChat);
|
||||||
|
expect(capturedArgs, ChatMessageType.importOpenApi);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Generate Tool hides and shows dashbot window even without response',
|
||||||
|
(tester) async {
|
||||||
|
final notifier = await _pumpHomePage(tester, onRoute: (_, __) {});
|
||||||
|
|
||||||
|
await tester.tap(find.text('🛠️ Generate Tool'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(notifier.hideCalls, 1);
|
||||||
|
expect(notifier.showCalls, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Generate UI opens dialog and restores dashbot window',
|
||||||
|
(tester) async {
|
||||||
|
final responseModel = const HttpResponseModel(
|
||||||
|
body: 'example response',
|
||||||
|
formattedBody: 'formatted',
|
||||||
|
);
|
||||||
|
final requestModel = RequestModel(
|
||||||
|
id: 'req-1',
|
||||||
|
httpRequestModel: const HttpRequestModel(),
|
||||||
|
httpResponseModel: responseModel,
|
||||||
|
);
|
||||||
|
|
||||||
|
final notifier = await _pumpHomePage(
|
||||||
|
tester,
|
||||||
|
selectedModel: requestModel,
|
||||||
|
onRoute: (_, __) {},
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.text('📱 Generate UI'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byType(Dialog), findsOneWidget);
|
||||||
|
|
||||||
|
final dialogElement = find.byType(Dialog);
|
||||||
|
if (dialogElement.evaluate().isNotEmpty) {
|
||||||
|
Navigator.of(dialogElement.evaluate().first).pop();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(notifier.hideCalls, 1);
|
||||||
|
expect(notifier.showCalls, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
51
test/dashbot/pages/test_utils.dart
Normal file
51
test/dashbot/pages/test_utils.dart
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import 'package:apidash/dashbot/features/chat/models/chat_message.dart';
|
||||||
|
import 'package:apidash/dashbot/features/chat/viewmodel/chat_viewmodel.dart';
|
||||||
|
import 'package:apidash/dashbot/features/chat/models/chat_state.dart';
|
||||||
|
import 'package:apidash/dashbot/core/constants/constants.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SpyChatViewmodel extends ChatViewmodel {
|
||||||
|
SpyChatViewmodel(super.ref);
|
||||||
|
|
||||||
|
final List<({String text, ChatMessageType type, bool countAsUser})>
|
||||||
|
sendMessageCalls = [];
|
||||||
|
|
||||||
|
bool clearCalled = false;
|
||||||
|
List<ChatMessage> _messages = const [];
|
||||||
|
|
||||||
|
void setMessages(List<ChatMessage> messages) {
|
||||||
|
_messages = messages;
|
||||||
|
state = state.copyWith(chatSessions: {'global': messages});
|
||||||
|
}
|
||||||
|
|
||||||
|
void setState(ChatState newState) {
|
||||||
|
state = newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<ChatMessage> get currentMessages => _messages;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> sendMessage({
|
||||||
|
required String text,
|
||||||
|
ChatMessageType type = ChatMessageType.general,
|
||||||
|
bool countAsUser = true,
|
||||||
|
}) async {
|
||||||
|
sendMessageCalls.add((text: text, type: type, countAsUser: countAsUser));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void clearCurrentChat() {
|
||||||
|
clearCalled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RecordingNavigatorObserver extends NavigatorObserver {
|
||||||
|
Route<dynamic>? lastRoute;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
|
||||||
|
super.didPush(route, previousRoute);
|
||||||
|
lastRoute = route;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user