From 74366454f877c3ef2c937fad5c58a7e1b8ff7afa Mon Sep 17 00:00:00 2001 From: Udhay-Adithya Date: Thu, 25 Sep 2025 18:46:16 +0530 Subject: [PATCH] tests: add tests for dashbot action buttons(cv: 95.5) --- .../dashbot_add_test_button_test.dart | 52 +++++ .../dashbot_apply_curl_button_test.dart | 106 +++++++++ .../dashbot_apply_openapi_button_test.dart | 104 +++++++++ .../dashbot_auto_fix_button_test.dart | 54 +++++ .../dashbot_download_doc_button_test.dart | 91 ++++++++ .../dashbot_generate_codeblock_test.dart | 54 +++++ ..._generate_language_picker_button_test.dart | 87 ++++++++ .../dashbot_import_now_button_test.dart | 96 +++++++++ .../dashbot_select_operation_button_test.dart | 79 +++++++ .../dashbot_upload_request_button_test.dart | 203 ++++++++++++++++++ .../widgets/action_buttons/test_utils.dart | 55 +++++ 11 files changed, 981 insertions(+) create mode 100644 test/dashbot/widgets/action_buttons/dashbot_add_test_button_test.dart create mode 100644 test/dashbot/widgets/action_buttons/dashbot_apply_curl_button_test.dart create mode 100644 test/dashbot/widgets/action_buttons/dashbot_apply_openapi_button_test.dart create mode 100644 test/dashbot/widgets/action_buttons/dashbot_auto_fix_button_test.dart create mode 100644 test/dashbot/widgets/action_buttons/dashbot_download_doc_button_test.dart create mode 100644 test/dashbot/widgets/action_buttons/dashbot_generate_codeblock_test.dart create mode 100644 test/dashbot/widgets/action_buttons/dashbot_generate_language_picker_button_test.dart create mode 100644 test/dashbot/widgets/action_buttons/dashbot_import_now_button_test.dart create mode 100644 test/dashbot/widgets/action_buttons/dashbot_select_operation_button_test.dart create mode 100644 test/dashbot/widgets/action_buttons/dashbot_upload_request_button_test.dart create mode 100644 test/dashbot/widgets/action_buttons/test_utils.dart diff --git a/test/dashbot/widgets/action_buttons/dashbot_add_test_button_test.dart b/test/dashbot/widgets/action_buttons/dashbot_add_test_button_test.dart new file mode 100644 index 00000000..75ebfb85 --- /dev/null +++ b/test/dashbot/widgets/action_buttons/dashbot_add_test_button_test.dart @@ -0,0 +1,52 @@ +import 'package:apidash/dashbot/core/common/widgets/dashbot_action_buttons/dashbot_add_test_button.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/viewmodel/chat_viewmodel.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../test_consts.dart'; +import 'test_utils.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const action = ChatAction( + action: 'add_test', + target: 'test', + actionType: ChatActionType.other, + targetType: ChatActionTarget.test, + ); + + group('DashbotAddTestButton', () { + testWidgets('renders label and invokes applyAutoFix on press', + (tester) async { + late TestChatViewmodel notifier; + await tester.pumpWidget( + ProviderScope( + overrides: [ + chatViewmodelProvider.overrideWith((ref) { + notifier = TestChatViewmodel(ref); + return notifier; + }), + ], + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotAddTestButton(action: action), + ), + ), + ), + ); + + expect(find.text('Add Test'), findsOneWidget); + expect(find.byIcon(Icons.playlist_add_check), findsOneWidget); + + await tester.tap(find.text('Add Test')); + await tester.pump(); + + expect(notifier.applyAutoFixCalls.single, same(action)); + }); + }); +} diff --git a/test/dashbot/widgets/action_buttons/dashbot_apply_curl_button_test.dart b/test/dashbot/widgets/action_buttons/dashbot_apply_curl_button_test.dart new file mode 100644 index 00000000..31566b80 --- /dev/null +++ b/test/dashbot/widgets/action_buttons/dashbot_apply_curl_button_test.dart @@ -0,0 +1,106 @@ +import 'package:apidash/dashbot/core/common/widgets/dashbot_action_buttons/dashbot_apply_curl_button.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/viewmodel/chat_viewmodel.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../test_consts.dart'; +import 'test_utils.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('DashbotApplyCurlButton', () { + testWidgets('uses default Apply label and triggers auto-fix', + (tester) async { + const action = ChatAction( + action: 'apply_curl', + target: 'httpRequestModel', + actionType: ChatActionType.applyCurl, + targetType: ChatActionTarget.httpRequestModel, + ); + + late TestChatViewmodel notifier; + await tester.pumpWidget( + ProviderScope( + overrides: [ + chatViewmodelProvider.overrideWith((ref) { + notifier = TestChatViewmodel(ref); + return notifier; + }), + ], + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotApplyCurlButton(action: action), + ), + ), + ), + ); + + expect(find.text('Apply'), findsOneWidget); + + await tester.tap(find.text('Apply')); + await tester.pump(); + + expect(notifier.applyAutoFixCalls, hasLength(1)); + expect(notifier.applyAutoFixCalls.single, same(action)); + }); + + testWidgets('shows Create New Request when field=apply_to_new', + (tester) async { + const action = ChatAction( + action: 'apply_curl', + target: 'httpRequestModel', + field: 'apply_to_new', + actionType: ChatActionType.applyCurl, + targetType: ChatActionTarget.httpRequestModel, + ); + + await tester.pumpWidget( + ProviderScope( + overrides: [ + chatViewmodelProvider.overrideWith((ref) => TestChatViewmodel(ref)), + ], + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotApplyCurlButton(action: action), + ), + ), + ), + ); + + expect(find.text('Create New Request'), findsOneWidget); + }); + + testWidgets('uses path text when selecting operation', (tester) async { + const action = ChatAction( + action: 'apply_curl', + target: 'httpRequestModel', + field: 'select_operation', + path: '/users', + actionType: ChatActionType.applyCurl, + targetType: ChatActionTarget.httpRequestModel, + ); + + await tester.pumpWidget( + ProviderScope( + overrides: [ + chatViewmodelProvider.overrideWith((ref) => TestChatViewmodel(ref)), + ], + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotApplyCurlButton(action: action), + ), + ), + ), + ); + + expect(find.text('/users'), findsOneWidget); + }); + }); +} diff --git a/test/dashbot/widgets/action_buttons/dashbot_apply_openapi_button_test.dart b/test/dashbot/widgets/action_buttons/dashbot_apply_openapi_button_test.dart new file mode 100644 index 00000000..01c20008 --- /dev/null +++ b/test/dashbot/widgets/action_buttons/dashbot_apply_openapi_button_test.dart @@ -0,0 +1,104 @@ +import 'package:apidash/dashbot/core/common/widgets/dashbot_action_buttons/dashbot_apply_openapi_button.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/viewmodel/chat_viewmodel.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../test_consts.dart'; +import 'test_utils.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('DashbotApplyOpenApiButton', () { + testWidgets('defaults to Apply label and invokes auto fix', (tester) async { + const action = ChatAction( + action: 'apply_openapi', + target: 'httpRequestModel', + actionType: ChatActionType.applyOpenApi, + targetType: ChatActionTarget.httpRequestModel, + ); + + late TestChatViewmodel notifier; + await tester.pumpWidget( + ProviderScope( + overrides: [ + chatViewmodelProvider.overrideWith((ref) { + notifier = TestChatViewmodel(ref); + return notifier; + }), + ], + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotApplyOpenApiButton(action: action), + ), + ), + ), + ); + + expect(find.text('Apply'), findsOneWidget); + + await tester.tap(find.text('Apply')); + await tester.pump(); + + expect(notifier.applyAutoFixCalls.single, same(action)); + }); + + testWidgets('shows Create New Request label when requested', + (tester) async { + const action = ChatAction( + action: 'apply_openapi', + target: 'httpRequestModel', + field: 'apply_to_new', + actionType: ChatActionType.applyOpenApi, + targetType: ChatActionTarget.httpRequestModel, + ); + + await tester.pumpWidget( + ProviderScope( + overrides: [ + chatViewmodelProvider.overrideWith((ref) => TestChatViewmodel(ref)), + ], + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotApplyOpenApiButton(action: action), + ), + ), + ), + ); + + expect(find.text('Create New Request'), findsOneWidget); + }); + + testWidgets('shows Apply to Selected label when field=apply_to_selected', + (tester) async { + const action = ChatAction( + action: 'apply_openapi', + target: 'httpRequestModel', + field: 'apply_to_selected', + actionType: ChatActionType.applyOpenApi, + targetType: ChatActionTarget.httpRequestModel, + ); + + await tester.pumpWidget( + ProviderScope( + overrides: [ + chatViewmodelProvider.overrideWith((ref) => TestChatViewmodel(ref)), + ], + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotApplyOpenApiButton(action: action), + ), + ), + ), + ); + + expect(find.text('Apply to Selected'), findsOneWidget); + }); + }); +} diff --git a/test/dashbot/widgets/action_buttons/dashbot_auto_fix_button_test.dart b/test/dashbot/widgets/action_buttons/dashbot_auto_fix_button_test.dart new file mode 100644 index 00000000..4e609a0b --- /dev/null +++ b/test/dashbot/widgets/action_buttons/dashbot_auto_fix_button_test.dart @@ -0,0 +1,54 @@ +import 'package:apidash/dashbot/core/common/widgets/dashbot_action_buttons/dashbot_auto_fix_button.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/viewmodel/chat_viewmodel.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../test_consts.dart'; +import 'test_utils.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const action = ChatAction( + action: 'update_field', + target: 'httpRequestModel', + field: 'url', + value: 'https://api.apidash.dev/users', + actionType: ChatActionType.updateField, + targetType: ChatActionTarget.httpRequestModel, + ); + + group('DashbotAutoFixButton', () { + testWidgets('renders icon, label and triggers auto-fix', (tester) async { + late TestChatViewmodel notifier; + await tester.pumpWidget( + ProviderScope( + overrides: [ + chatViewmodelProvider.overrideWith((ref) { + notifier = TestChatViewmodel(ref); + return notifier; + }), + ], + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotAutoFixButton(action: action), + ), + ), + ), + ); + + expect(find.text('Auto Fix'), findsOneWidget); + expect(find.byIcon(Icons.auto_fix_high), findsOneWidget); + + await tester.tap(find.text('Auto Fix')); + await tester.pump(); + + expect(notifier.applyAutoFixCalls, hasLength(1)); + expect(notifier.applyAutoFixCalls.single, same(action)); + }); + }); +} diff --git a/test/dashbot/widgets/action_buttons/dashbot_download_doc_button_test.dart b/test/dashbot/widgets/action_buttons/dashbot_download_doc_button_test.dart new file mode 100644 index 00000000..8547d730 --- /dev/null +++ b/test/dashbot/widgets/action_buttons/dashbot_download_doc_button_test.dart @@ -0,0 +1,91 @@ +import 'dart:io'; + +import 'package:apidash/dashbot/core/common/widgets/dashbot_action_buttons/dashbot_download_doc_button.dart'; +import 'package:apidash/dashbot/core/constants/constants.dart'; +import 'package:apidash/dashbot/features/chat/models/chat_action.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../test_consts.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late Directory tempDownloadsDir; + + setUp(() { + tempDownloadsDir = Directory.systemTemp.createTempSync('dashbot_download'); + }); + + tearDown(() { + if (tempDownloadsDir.existsSync()) { + tempDownloadsDir.deleteSync(recursive: true); + } + }); + + group('DashbotDownloadDocButton', () { + testWidgets('disables download when content is empty', (tester) async { + const action = ChatAction( + action: 'download_doc', + target: 'code', + path: 'doc', + value: '', + actionType: ChatActionType.downloadDoc, + targetType: ChatActionTarget.code, + ); + + await tester.pumpWidget( + ProviderScope( + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotDownloadDocButton(action: action), + ), + ), + ), + ); + + final buttonFinder = + find.byWidgetPredicate((widget) => widget is ButtonStyleButton); + final button = tester.widget(buttonFinder); + expect(button.onPressed, isNull); + }); + + // testWidgets('saves documentation to downloads directory', (tester) async { + // const action = ChatAction( + // action: 'download_doc', + // target: 'code', + // path: 'api-docs', + // value: '# Markdown', + // actionType: ChatActionType.downloadDoc, + // targetType: ChatActionTarget.code, + // ); + + // await tester.pumpWidget( + // ProviderScope( + // child: MaterialApp( + // theme: kThemeDataLight, + // home: const Scaffold( + // body: DashbotDownloadDocButton(action: action), + // ), + // ), + // ), + // ); + + // final buttonFinder = + // find.byWidgetPredicate((widget) => widget is ButtonStyleButton); + // final button = tester.widget(buttonFinder); + // expect(button.onPressed, isNotNull); + + // await tester.tap(find.text('Download Documentation')); + // await tester.pumpAndSettle(); + + // final savedPath = + // '${tempDownloadsDir.path}${Platform.pathSeparator}api-docs.md'; + // final savedFile = File(savedPath); + // expect(savedFile.existsSync(), isTrue); + // expect(savedFile.readAsStringSync(), '# Markdown'); + // }); + }); +} diff --git a/test/dashbot/widgets/action_buttons/dashbot_generate_codeblock_test.dart b/test/dashbot/widgets/action_buttons/dashbot_generate_codeblock_test.dart new file mode 100644 index 00000000..63740c50 --- /dev/null +++ b/test/dashbot/widgets/action_buttons/dashbot_generate_codeblock_test.dart @@ -0,0 +1,54 @@ +import 'package:apidash/dashbot/core/common/widgets/dashbot_action_buttons/dashbot_generate_codeblock.dart'; +import 'package:apidash/dashbot/core/constants/constants.dart'; +import 'package:apidash/dashbot/features/chat/models/chat_action.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../test_consts.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('DashbotGeneratedCodeBlock', () { + testWidgets('shows provided code value', (tester) async { + const action = ChatAction( + action: 'other', + target: 'code', + value: 'print("hello")', + actionType: ChatActionType.other, + targetType: ChatActionTarget.code, + ); + + await tester.pumpWidget( + MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotGeneratedCodeBlock(action: action), + ), + ), + ); + + expect(find.text('print("hello")'), findsOneWidget); + }); + + testWidgets('falls back to placeholder when value missing', (tester) async { + const action = ChatAction( + action: 'other', + target: 'code', + actionType: ChatActionType.other, + targetType: ChatActionTarget.code, + ); + + await tester.pumpWidget( + MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotGeneratedCodeBlock(action: action), + ), + ), + ); + + expect(find.text('// No code returned'), findsOneWidget); + }); + }); +} diff --git a/test/dashbot/widgets/action_buttons/dashbot_generate_language_picker_button_test.dart b/test/dashbot/widgets/action_buttons/dashbot_generate_language_picker_button_test.dart new file mode 100644 index 00000000..bb323be3 --- /dev/null +++ b/test/dashbot/widgets/action_buttons/dashbot_generate_language_picker_button_test.dart @@ -0,0 +1,87 @@ +import 'package:apidash/dashbot/core/common/widgets/dashbot_action_buttons/dashbot_generate_language_picker_button.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/viewmodel/chat_viewmodel.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../test_consts.dart'; +import 'test_utils.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('DashbotGenerateLanguagePicker', () { + testWidgets('renders provided language options and sends message on tap', + (tester) async { + const action = ChatAction( + action: 'show_languages', + target: 'codegen', + value: ['Python (requests)', 'cURL'], + actionType: ChatActionType.showLanguages, + targetType: ChatActionTarget.codegen, + ); + + late TestChatViewmodel notifier; + await tester.pumpWidget( + ProviderScope( + overrides: [ + chatViewmodelProvider.overrideWith((ref) { + notifier = TestChatViewmodel(ref); + return notifier; + }), + ], + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotGenerateLanguagePicker(action: action), + ), + ), + ), + ); + + expect(find.text('Python (requests)'), findsOneWidget); + expect(find.text('cURL'), findsOneWidget); + + await tester.tap(find.text('Python (requests)')); + await tester.pump(); + + expect(notifier.sendMessageCalls, hasLength(1)); + final call = notifier.sendMessageCalls.single; + expect(call.text, 'Please generate code in Python (requests)'); + expect(call.type, ChatMessageType.generateCode); + expect(call.countAsUser, isTrue); + }); + + testWidgets('falls back to default language list', (tester) async { + const action = ChatAction( + action: 'show_languages', + target: 'codegen', + value: 'unexpected', + actionType: ChatActionType.showLanguages, + targetType: ChatActionTarget.codegen, + ); + + await tester.pumpWidget( + ProviderScope( + overrides: [ + chatViewmodelProvider.overrideWith((ref) => TestChatViewmodel(ref)), + ], + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotGenerateLanguagePicker(action: action), + ), + ), + ), + ); + + expect(find.text('JavaScript (fetch)'), findsOneWidget); + expect(find.text('Python (requests)'), findsOneWidget); + expect(find.text('Dart (http)'), findsOneWidget); + expect(find.text('Go (net/http)'), findsOneWidget); + expect(find.text('cURL'), findsOneWidget); + }); + }); +} diff --git a/test/dashbot/widgets/action_buttons/dashbot_import_now_button_test.dart b/test/dashbot/widgets/action_buttons/dashbot_import_now_button_test.dart new file mode 100644 index 00000000..35215093 --- /dev/null +++ b/test/dashbot/widgets/action_buttons/dashbot_import_now_button_test.dart @@ -0,0 +1,96 @@ +import 'dart:ui'; + +import 'package:apidash/dashbot/core/common/widgets/dashbot_action_buttons/dashbot_import_now_button.dart'; +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_action.dart'; +import 'package:apidash/dashbot/features/chat/viewmodel/chat_viewmodel.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../test_consts.dart'; +import 'test_utils.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const specJson = ''' +{ + "openapi": "3.0.0", + "info": {"title": "Sample API", "version": "1.0.0"}, + "servers": [{"url": "https://api.apidash.dev"}], + "paths": { + "/users": { + "get": { + "responses": {"200": {"description": "OK"}} + } + } + } +} +'''; + + group('DashbotImportNowButton', () { + testWidgets('hides dashbot window, shows picker, applies selected ops', + (tester) async { + const action = ChatAction( + action: 'import_now_openapi', + target: 'httpRequestModel', + value: {'sourceName': 'Sample', 'content': specJson}, + actionType: ChatActionType.other, + targetType: ChatActionTarget.httpRequestModel, + ); + + late TestChatViewmodel notifier; + late RecordingDashbotWindowNotifier windowNotifier; + final binding = TestWidgetsFlutterBinding.ensureInitialized(); + binding.window.physicalSizeTestValue = const Size(1600, 1200); + binding.window.devicePixelRatioTestValue = 1.0; + addTearDown(() { + binding.window.clearPhysicalSizeTestValue(); + binding.window.clearDevicePixelRatioTestValue(); + }); + await tester.pumpWidget( + ProviderScope( + overrides: [ + chatViewmodelProvider.overrideWith((ref) { + notifier = TestChatViewmodel(ref); + return notifier; + }), + dashbotWindowNotifierProvider.overrideWith((ref) { + windowNotifier = RecordingDashbotWindowNotifier(); + return windowNotifier; + }), + ], + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotImportNowButton(action: action), + ), + ), + ), + ); + + await tester.tap(find.text('Import Now')); + await tester.pumpAndSettle(); + + // Dialog should be shown; tap Import to accept default selection + final importFinder = find.text('Import'); + expect(importFinder, findsOneWidget); + await tester.tap(importFinder); + await tester.pumpAndSettle(); + + expect(windowNotifier.hideCalls, greaterThanOrEqualTo(1)); + expect(windowNotifier.showCalls, greaterThanOrEqualTo(1)); + + expect(notifier.applyAutoFixCalls, isNotEmpty); + final applied = notifier.applyAutoFixCalls.single; + expect(applied.action, 'apply_openapi'); + expect(applied.field, 'apply_to_new'); + expect(applied.value, isA>()); + final value = applied.value as Map; + expect(value['url'], 'https://api.apidash.dev/users'); + expect(value['method'], 'GET'); + }); + }); +} diff --git a/test/dashbot/widgets/action_buttons/dashbot_select_operation_button_test.dart b/test/dashbot/widgets/action_buttons/dashbot_select_operation_button_test.dart new file mode 100644 index 00000000..c584fdfb --- /dev/null +++ b/test/dashbot/widgets/action_buttons/dashbot_select_operation_button_test.dart @@ -0,0 +1,79 @@ +import 'package:apidash/dashbot/core/common/widgets/dashbot_action_buttons/dsahbot_select_operation_button.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/viewmodel/chat_viewmodel.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../../test_consts.dart'; +import 'test_utils.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('DashbotSelectOperationButton', () { + testWidgets('renders operation label and applies auto fix', (tester) async { + const action = ChatAction( + action: 'select_operation', + target: 'httpRequestModel', + field: 'select_operation', + path: 'GET /users', + actionType: ChatActionType.applyOpenApi, + targetType: ChatActionTarget.httpRequestModel, + ); + + late TestChatViewmodel notifier; + await tester.pumpWidget( + ProviderScope( + overrides: [ + chatViewmodelProvider.overrideWith((ref) { + notifier = TestChatViewmodel(ref); + return notifier; + }), + ], + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotSelectOperationButton(action: action), + ), + ), + ), + ); + + expect(find.text('GET /users'), findsOneWidget); + + await tester.tap(find.text('GET /users')); + await tester.pump(); + + expect(notifier.applyAutoFixCalls.single, same(action)); + }); + + testWidgets('falls back to Unknown label when path missing', + (tester) async { + const action = ChatAction( + action: 'select_operation', + target: 'httpRequestModel', + field: 'select_operation', + actionType: ChatActionType.applyOpenApi, + targetType: ChatActionTarget.httpRequestModel, + ); + + await tester.pumpWidget( + ProviderScope( + overrides: [ + chatViewmodelProvider.overrideWith((ref) => TestChatViewmodel(ref)), + ], + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotSelectOperationButton(action: action), + ), + ), + ), + ); + + expect(find.text('Unknown'), findsOneWidget); + }); + }); +} diff --git a/test/dashbot/widgets/action_buttons/dashbot_upload_request_button_test.dart b/test/dashbot/widgets/action_buttons/dashbot_upload_request_button_test.dart new file mode 100644 index 00000000..f75f4c2a --- /dev/null +++ b/test/dashbot/widgets/action_buttons/dashbot_upload_request_button_test.dart @@ -0,0 +1,203 @@ +import 'dart:typed_data'; + +import 'package:file_selector_platform_interface/file_selector_platform_interface.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:apidash/dashbot/core/common/widgets/dashbot_action_buttons/dashbot_upload_requests_button.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/viewmodel/chat_viewmodel.dart'; + +import '../../../test_consts.dart'; +import 'test_utils.dart'; + +class FakeFileSelectorPlatform extends FileSelectorPlatform { + FakeFileSelectorPlatform({this.fileToReturn}); + + XFile? fileToReturn; + List? lastAcceptedTypeGroups; + + @override + Future openFile({ + List? acceptedTypeGroups, + String? initialDirectory, + String? confirmButtonText, + }) async { + lastAcceptedTypeGroups = acceptedTypeGroups; + return fileToReturn; + } + + @override + Future> openFiles({ + List? acceptedTypeGroups, + String? initialDirectory, + String? confirmButtonText, + }) async { + lastAcceptedTypeGroups = acceptedTypeGroups; + return fileToReturn == null ? [] : [fileToReturn!]; + } + + @override + Future getDirectoryPath({ + String? initialDirectory, + String? confirmButtonText, + }) async => + null; + + @override + Future> getDirectoryPaths({ + String? initialDirectory, + String? confirmButtonText, + }) async => + []; + + @override + Future getSavePath({ + List? acceptedTypeGroups, + String? initialDirectory, + String? suggestedName, + String? confirmButtonText, + }) async => + null; + + @override + Future getSaveLocation({ + List? acceptedTypeGroups, + SaveDialogOptions options = const SaveDialogOptions(), + }) async => + null; +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late FakeFileSelectorPlatform fakePlatform; + + setUp(() { + fakePlatform = FakeFileSelectorPlatform(); + FileSelectorPlatform.instance = fakePlatform; + }); + + group('DashbotUploadRequestButton', () { + testWidgets('uploads OpenAPI attachment and forwards to chat viewmodel', + (tester) async { + final bytes = Uint8List.fromList('openapi spec'.codeUnits); + fakePlatform.fileToReturn = XFile.fromData(bytes, + name: 'spec.yaml', mimeType: 'application/yaml'); + + const action = ChatAction( + action: 'upload_asset', + target: 'attachment', + field: 'openapi_spec', + value: { + 'purpose': 'OpenAPI specification', + 'accepted_types': ['application/yaml'] + }, + actionType: ChatActionType.uploadAsset, + targetType: ChatActionTarget.attachment, + ); + + late TestChatViewmodel notifier; + await tester.pumpWidget( + ProviderScope( + overrides: [ + chatViewmodelProvider.overrideWith((ref) { + notifier = TestChatViewmodel(ref); + return notifier; + }), + ], + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotUploadRequestButton(action: action), + ), + ), + ), + ); + + await tester.tap(find.text('Upload: OpenAPI specification')); + await tester.pump(); + + expect(fakePlatform.lastAcceptedTypeGroups, isNotNull); + final group = fakePlatform.lastAcceptedTypeGroups!.single; + expect(group.mimeTypes, ['application/yaml']); + + expect(notifier.openApiAttachmentCalls, hasLength(1)); + final attachment = notifier.openApiAttachmentCalls.single; + expect(attachment.mimeType, 'application/yaml'); + expect(attachment.sizeBytes, bytes.length); + }); + + testWidgets('sends message for non-openapi uploads', (tester) async { + fakePlatform.fileToReturn = + XFile.fromData(Uint8List.fromList([1, 2, 3]), name: 'data.bin'); + + const action = ChatAction( + action: 'upload_asset', + target: 'attachment', + value: {'purpose': 'Sample upload'}, + actionType: ChatActionType.uploadAsset, + targetType: ChatActionTarget.attachment, + ); + + late TestChatViewmodel notifier; + await tester.pumpWidget( + ProviderScope( + overrides: [ + chatViewmodelProvider.overrideWith((ref) { + notifier = TestChatViewmodel(ref); + return notifier; + }), + ], + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotUploadRequestButton(action: action), + ), + ), + ), + ); + + await tester.tap(find.text('Upload: Sample upload')); + await tester.pump(); + + expect(notifier.sendMessageCalls, hasLength(1)); + final call = notifier.sendMessageCalls.single; + expect(call.type, ChatMessageType.general); + expect(call.text, contains('Attached file')); + expect(call.text, contains('size=3')); + }); + + testWidgets('does nothing when user cancels file picker', (tester) async { + fakePlatform.fileToReturn = null; + + const action = ChatAction( + action: 'upload_asset', + target: 'attachment', + actionType: ChatActionType.uploadAsset, + targetType: ChatActionTarget.attachment, + ); + + await tester.pumpWidget( + ProviderScope( + overrides: [ + chatViewmodelProvider.overrideWith((ref) => TestChatViewmodel(ref)), + ], + child: MaterialApp( + theme: kThemeDataLight, + home: Scaffold( + body: DashbotUploadRequestButton(action: action), + ), + ), + ), + ); + + await tester.tap(find.text('Upload Attachment')); + await tester.pump(); + + // No exceptions and no attachments created + }); + }); +} diff --git a/test/dashbot/widgets/action_buttons/test_utils.dart b/test/dashbot/widgets/action_buttons/test_utils.dart new file mode 100644 index 00000000..17d83d86 --- /dev/null +++ b/test/dashbot/widgets/action_buttons/test_utils.dart @@ -0,0 +1,55 @@ +import 'package:apidash/dashbot/core/model/chat_attachment.dart'; +import 'package:apidash/dashbot/core/providers/dashbot_window_notifier.dart'; +import 'package:apidash/dashbot/features/chat/models/chat_action.dart'; +import 'package:apidash/dashbot/features/chat/viewmodel/chat_viewmodel.dart'; +import 'package:apidash/dashbot/core/constants/constants.dart'; + +class TestChatViewmodel extends ChatViewmodel { + TestChatViewmodel(super.ref); + + final List applyAutoFixCalls = []; + final List<({String text, ChatMessageType type, bool countAsUser})> + sendMessageCalls = []; + final List openApiAttachmentCalls = []; + + bool throwOnApplyAutoFix = false; + + @override + Future applyAutoFix(ChatAction action) async { + applyAutoFixCalls.add(action); + if (throwOnApplyAutoFix) { + throw Exception('applyAutoFix error'); + } + } + + @override + Future sendMessage({ + required String text, + ChatMessageType type = ChatMessageType.general, + bool countAsUser = true, + }) async { + sendMessageCalls.add((text: text, type: type, countAsUser: countAsUser)); + } + + @override + Future handleOpenApiAttachment(ChatAttachment att) async { + openApiAttachmentCalls.add(att); + } +} + +class RecordingDashbotWindowNotifier extends DashbotWindowNotifier { + int hideCalls = 0; + int showCalls = 0; + + @override + void hide() { + hideCalls += 1; + super.hide(); + } + + @override + void show() { + showCalls += 1; + super.show(); + } +}