mirror of
https://github.com/foss42/apidash.git
synced 2025-12-11 15:49:48 +08:00
feat: add dashbot widget tests(cv: 99)
This commit is contained in:
120
test/dashbot/widgets/chat/chat_bubble_test.dart
Normal file
120
test/dashbot/widgets/chat/chat_bubble_test.dart
Normal file
@@ -0,0 +1,120 @@
|
||||
import 'package:apidash/dashbot/core/common/widgets/dashbot_action_buttons/dashbot_actions_buttons.dart';
|
||||
import 'package:apidash/dashbot/core/constants/constants.dart';
|
||||
import 'package:apidash/dashbot/features/chat/models/chat_action.dart';
|
||||
import 'package:apidash/dashbot/features/chat/view/widgets/chat_bubble.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() async {
|
||||
await Clipboard.setData(const ClipboardData(text: ''));
|
||||
});
|
||||
|
||||
testWidgets('ChatBubble skips duplicate prompt override for user messages',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Scaffold(
|
||||
body: ChatBubble(
|
||||
message: 'duplicate',
|
||||
role: MessageRole.user,
|
||||
promptOverride: 'duplicate',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('duplicate'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('ChatBubble shows loading indicator when message empty',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Scaffold(
|
||||
body: ChatBubble(
|
||||
message: '',
|
||||
role: MessageRole.system,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('ChatBubble renders explanation parsed from system JSON',
|
||||
(tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Scaffold(
|
||||
body: ChatBubble(
|
||||
message: '{"explnation":"Parsed output"}',
|
||||
role: MessageRole.system,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final markdown =
|
||||
tester.widget<MarkdownBody>(find.byType(MarkdownBody).first);
|
||||
expect(markdown.data, 'Parsed output');
|
||||
});
|
||||
|
||||
testWidgets('ChatBubble renders action widgets when provided',
|
||||
(tester) async {
|
||||
const action = ChatAction(
|
||||
action: 'download_doc',
|
||||
target: 'documentation',
|
||||
actionType: ChatActionType.downloadDoc,
|
||||
targetType: ChatActionTarget.documentation,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Scaffold(
|
||||
body: ChatBubble(
|
||||
message: 'Here is your document',
|
||||
role: MessageRole.system,
|
||||
actions: [action],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byType(DashbotDownloadDocButton), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Copy icon copies rendered message to clipboard', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
const ProviderScope(
|
||||
child: MaterialApp(
|
||||
home: Scaffold(
|
||||
body: ChatBubble(
|
||||
message: 'Copy this please',
|
||||
role: MessageRole.system,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.byIcon(Icons.copy_rounded));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// TODO: //TODO: The below test works for `flutter run` but not for `flutter test`
|
||||
// final data = await Clipboard.getData('text/plain');
|
||||
// expect(data?.text, 'Copy this please');
|
||||
});
|
||||
}
|
||||
132
test/dashbot/widgets/chat/dashbot_task_buttons_test.dart
Normal file
132
test/dashbot/widgets/chat/dashbot_task_buttons_test.dart
Normal file
@@ -0,0 +1,132 @@
|
||||
import 'package:apidash/dashbot/core/constants/constants.dart';
|
||||
import 'package:apidash/dashbot/core/providers/dashbot_window_notifier.dart';
|
||||
import 'package:apidash/dashbot/features/chat/models/chat_state.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/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 '../../pages/test_utils.dart';
|
||||
import '../action_buttons/test_utils.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('DashbotTaskButtons quick actions dispatch expected commands',
|
||||
(tester) async {
|
||||
late SpyChatViewmodel spy;
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
chatViewmodelProvider.overrideWith((ref) {
|
||||
spy = SpyChatViewmodel(ref);
|
||||
spy.setState(const ChatState());
|
||||
return spy;
|
||||
}),
|
||||
dashbotWindowNotifierProvider
|
||||
.overrideWith((ref) => RecordingDashbotWindowNotifier()),
|
||||
selectedRequestModelProvider.overrideWith((ref) => null),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
home: Scaffold(body: DashbotTaskButtons()),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const sequence = {
|
||||
'🔎 Explain me this response': ChatMessageType.explainResponse,
|
||||
'🐞 Help me debug this error': ChatMessageType.debugError,
|
||||
'📄 Generate documentation': ChatMessageType.generateDoc,
|
||||
'📝 Generate Tests': ChatMessageType.generateTest,
|
||||
'🧩 Generate Code': ChatMessageType.generateCode,
|
||||
'📥 Import cURL': ChatMessageType.importCurl,
|
||||
'📄 Import OpenAPI': ChatMessageType.importOpenApi,
|
||||
};
|
||||
|
||||
for (final entry in sequence.entries) {
|
||||
spy.sendMessageCalls.clear();
|
||||
await tester.tap(find.text(entry.key));
|
||||
await tester.pump();
|
||||
|
||||
expect(spy.sendMessageCalls.length, 1,
|
||||
reason: 'Expected a call for ${entry.key}');
|
||||
expect(spy.sendMessageCalls.single.type, entry.value);
|
||||
expect(spy.sendMessageCalls.single.countAsUser, isFalse);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('DashbotTaskButtons generate tool toggles window visibility',
|
||||
(tester) async {
|
||||
late SpyChatViewmodel spy;
|
||||
final windowNotifier = RecordingDashbotWindowNotifier();
|
||||
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
chatViewmodelProvider.overrideWith((ref) {
|
||||
spy = SpyChatViewmodel(ref);
|
||||
spy.setState(const ChatState());
|
||||
return spy;
|
||||
}),
|
||||
dashbotWindowNotifierProvider.overrideWith((ref) => windowNotifier),
|
||||
selectedRequestModelProvider.overrideWith((ref) => null),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
home: Scaffold(body: DashbotTaskButtons()),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('🛠️ Generate Tool'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(windowNotifier.hideCalls, 1);
|
||||
expect(windowNotifier.showCalls, 1);
|
||||
expect(spy.sendMessageCalls, isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('DashbotTaskButtons generate UI opens dialog and restores window',
|
||||
(tester) async {
|
||||
late SpyChatViewmodel spy;
|
||||
final windowNotifier = RecordingDashbotWindowNotifier();
|
||||
final requestModel = RequestModel(
|
||||
id: 'req-2',
|
||||
httpRequestModel: const HttpRequestModel(),
|
||||
httpResponseModel: const HttpResponseModel(body: 'response body'),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
chatViewmodelProvider.overrideWith((ref) {
|
||||
spy = SpyChatViewmodel(ref);
|
||||
spy.setState(const ChatState());
|
||||
return spy;
|
||||
}),
|
||||
dashbotWindowNotifierProvider.overrideWith((ref) => windowNotifier),
|
||||
selectedRequestModelProvider.overrideWith((ref) => requestModel),
|
||||
],
|
||||
child: const MaterialApp(
|
||||
home: Scaffold(body: DashbotTaskButtons()),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
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(windowNotifier.hideCalls, 1);
|
||||
expect(windowNotifier.showCalls, 1);
|
||||
expect(spy.sendMessageCalls, isEmpty);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
import 'package:apidash/dashbot/features/chat/view/widgets/openapi_operation_picker_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:openapi_spec/openapi_spec.dart';
|
||||
|
||||
const _emptySpecJson = '''
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {"title": "Empty", "version": "1.0.0"},
|
||||
"paths": {}
|
||||
}
|
||||
''';
|
||||
|
||||
const _sampleSpecJson = '''
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {"title": "Sample", "version": "1.0.0"},
|
||||
"paths": {
|
||||
"/users": {
|
||||
"get": {"responses": {"200": {"description": "ok"}}},
|
||||
"post": {"responses": {"201": {"description": "created"}}}
|
||||
}
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
void main() {
|
||||
OpenApi _parse(String json) => OpenApi.fromString(source: json, format: null);
|
||||
|
||||
testWidgets('returns empty selection when spec has no operations',
|
||||
(tester) async {
|
||||
final spec = _parse(_emptySpecJson);
|
||||
|
||||
List<OpenApiOperationItem>? resolved;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Builder(
|
||||
builder: (context) {
|
||||
showOpenApiOperationPickerDialog(
|
||||
context: context,
|
||||
spec: spec,
|
||||
sourceName: 'Empty spec',
|
||||
).then((value) => resolved = value);
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pump();
|
||||
|
||||
expect(resolved, isNotNull);
|
||||
expect(resolved, isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('allows toggling select-all and individual operations',
|
||||
(tester) async {
|
||||
final spec = _parse(_sampleSpecJson);
|
||||
|
||||
late Future<List<OpenApiOperationItem>?> dialogFuture;
|
||||
|
||||
final binding = tester.binding;
|
||||
await binding.setSurfaceSize(const Size(1200, 1000));
|
||||
addTearDown(() => binding.setSurfaceSize(null));
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
return Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
dialogFuture = showOpenApiOperationPickerDialog(
|
||||
context: context,
|
||||
spec: spec,
|
||||
sourceName: 'Sample spec',
|
||||
);
|
||||
},
|
||||
child: const Text('Launch dialog'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Launch dialog'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final selectAllFinder = find.widgetWithText(CheckboxListTile, 'Select all');
|
||||
final importFinder = find.widgetWithText(FilledButton, 'Import');
|
||||
|
||||
// Initial state: everything selected → import enabled
|
||||
expect(
|
||||
tester.widget<FilledButton>(importFinder).onPressed,
|
||||
isNotNull,
|
||||
);
|
||||
|
||||
// Toggle "Select all" off → deselect everything & disable import
|
||||
await tester.tap(selectAllFinder);
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
tester.widget<FilledButton>(importFinder).onPressed,
|
||||
isNull,
|
||||
);
|
||||
|
||||
// Toggle "Select all" back on → reselect all and enable import
|
||||
await tester.tap(selectAllFinder);
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
tester.widget<FilledButton>(importFinder).onPressed,
|
||||
isNotNull,
|
||||
);
|
||||
|
||||
final usersOpFinder = find.text('GET /users');
|
||||
expect(usersOpFinder, findsOneWidget);
|
||||
|
||||
// Uncheck a single operation → coverage for removal branch
|
||||
await tester.tap(usersOpFinder);
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
tester.widget<FilledButton>(importFinder).onPressed,
|
||||
isNotNull,
|
||||
);
|
||||
|
||||
// Check it again → coverage for addition branch
|
||||
await tester.tap(usersOpFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(importFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final result = await dialogFuture;
|
||||
expect(result, isNotNull);
|
||||
expect(result, hasLength(2));
|
||||
expect(result!.map((item) => item.method), containsAll(['GET', 'POST']));
|
||||
});
|
||||
|
||||
testWidgets('returns null when cancelled', (tester) async {
|
||||
final spec = _parse(_sampleSpecJson);
|
||||
|
||||
late Future<List<OpenApiOperationItem>?> dialogFuture;
|
||||
|
||||
final binding = tester.binding;
|
||||
await binding.setSurfaceSize(const Size(1200, 1000));
|
||||
addTearDown(() => binding.setSurfaceSize(null));
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
return Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
dialogFuture = showOpenApiOperationPickerDialog(
|
||||
context: context,
|
||||
spec: spec,
|
||||
sourceName: 'Sample spec',
|
||||
);
|
||||
},
|
||||
child: const Text('Launch dialog'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('Launch dialog'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.widgetWithText(TextButton, 'Cancel'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final result = await dialogFuture;
|
||||
expect(result, isNull);
|
||||
});
|
||||
}
|
||||
30
test/dashbot/widgets/home/home_screen_task_button_test.dart
Normal file
30
test/dashbot/widgets/home/home_screen_task_button_test.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
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';
|
||||
|
||||
void main() {
|
||||
testWidgets('HomeScreenTaskButton renders label and invokes callback',
|
||||
(tester) async {
|
||||
var tapped = false;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: HomeScreenTaskButton(
|
||||
label: 'Perform action',
|
||||
textAlign: TextAlign.left,
|
||||
onPressed: () => tapped = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('Perform action'), findsOneWidget);
|
||||
final textWidget = tester.widget<Text>(find.text('Perform action'));
|
||||
expect(textWidget.textAlign, TextAlign.left);
|
||||
|
||||
await tester.tap(find.byType(TextButton));
|
||||
await tester.pump();
|
||||
|
||||
expect(tapped, isTrue);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user