diff --git a/.gitignore b/.gitignore index 4bdfd3292..517a3ab21 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,6 @@ coverage/* installers/* .metadata .fvm/ + +# unit tests hive storage +test/unit-test-hive-storage/ diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index db6b7cf33..4b658322c 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -145,6 +145,15 @@ class CollectionStateNotifier String? message, ResponseModel? responseModel, }) { + // Switch from GET to POST if request has payload of any kind + // primarily, text, json and formData + if ((requestBody != null || requestFormDataList != null) && + state![id]!.method == HTTPVerb.get) { + ref.read(autoSwitchPOSTStateProvider.notifier).state = true; + method = HTTPVerb.post; + ref.read(autoSwitchPOSTStateProvider.notifier).state = false; + } + final newModel = state![id]!.copyWith( method: method, url: url, diff --git a/lib/providers/ui_providers.dart b/lib/providers/ui_providers.dart index 2fddb2ad7..511b28886 100644 --- a/lib/providers/ui_providers.dart +++ b/lib/providers/ui_providers.dart @@ -6,6 +6,11 @@ final selectedIdEditStateProvider = StateProvider((ref) => null); final codePaneVisibleStateProvider = StateProvider((ref) => false); final saveDataStateProvider = StateProvider((ref) => false); final clearDataStateProvider = StateProvider((ref) => false); + +/// As part of UX, when body is added to GET requests, they're +/// auto converted to POST requests. UI Snackbar trigger for the same +final autoSwitchPOSTStateProvider = StateProvider((ref) => false); + // final nameTextFieldControllerProvider = // StateProvider.autoDispose((ref) { // TextEditingController controller = TextEditingController(text: ""); diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane.dart index 337f0785b..0559a8ea0 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/widgets.dart'; @@ -14,7 +15,8 @@ class EditRequestPane extends ConsumerWidget { final selectedId = ref.watch(selectedIdStateProvider); final codePaneVisible = ref.watch(codePaneVisibleStateProvider); final tabIndex = ref.watch( - selectedRequestModelProvider.select((value) => value?.requestTabIndex)); + selectedRequestModelProvider.select((value) => value?.requestTabIndex), + ); final headerLength = ref.watch(selectedRequestModelProvider .select((value) => value?.headersMap.length)) ?? @@ -26,6 +28,15 @@ class EditRequestPane extends ConsumerWidget { selectedRequestModelProvider.select((value) => value?.hasBody)) ?? false; + // show snackbar when auto switch triggers + ref.listen(autoSwitchPOSTStateProvider, (_, currentValue) { + if (currentValue) { + final sm = ScaffoldMessenger.of(context); + sm.hideCurrentSnackBar(); + sm.showSnackBar(getSnackBar('Switching to POST Request')); + } + }); + return RequestPane( selectedId: selectedId, codePaneVisible: codePaneVisible, diff --git a/test/providers/collection_providers_test.dart b/test/providers/collection_providers_test.dart new file mode 100644 index 000000000..129d7e8ed --- /dev/null +++ b/test/providers/collection_providers_test.dart @@ -0,0 +1,28 @@ +import 'package:apidash/consts.dart'; +import 'package:apidash/providers/collection_providers.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'helpers.dart'; + +void main() async { + setUp(() async => await testSetUp()); + + test('Request method changes from GET to POST when body is added', () { + // Create a ProviderContainer for this test. + // DO NOT share ProviderContainers between tests. + final container = createContainer(); + final ref = container.read(collectionStateNotifierProvider.notifier); + + // One API request is preloaded always + final id = ref.state!.entries.first.key; + + // preloaded API is a GET request + expect(ref.getRequestModel(id)!.method, HTTPVerb.get); + expect(ref.getRequestModel(id)!.requestBody, null); + + // add request body + ref.update(id, requestBody: 'body'); + + // vertify new model is POST + expect(ref.getRequestModel(id)!.method, HTTPVerb.post); + }); +} diff --git a/test/providers/helpers.dart b/test/providers/helpers.dart new file mode 100644 index 000000000..7aa694e43 --- /dev/null +++ b/test/providers/helpers.dart @@ -0,0 +1,39 @@ +import 'package:apidash/services/hive_services.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +/// A testing utility which creates a [ProviderContainer] and automatically +/// disposes it at the end of the test. +ProviderContainer createContainer({ + ProviderContainer? parent, + List overrides = const [], + List? observers, +}) { + // Create a ProviderContainer, and optionally allow specifying parameters. + final container = ProviderContainer( + parent: parent, + overrides: overrides, + observers: observers, + ); + + // When the test ends, dispose the container. + addTearDown(container.dispose); + + return container; +} + +Future testSetUp() async { + // override path_provider methodCall to point + // path to temporary location for all unit tests + TestWidgetsFlutterBinding.ensureInitialized(); + const MethodChannel channel = + MethodChannel('plugins.flutter.io/path_provider'); + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + return './test/unit-test-hive-storage/'; + }); + + await openBoxes(); +}