From 21f927d8c3b5ecb05d946417a5354d8c7c1f8aad Mon Sep 17 00:00:00 2001 From: Deepak Bhagat Date: Sun, 28 Jun 2026 17:32:47 +0530 Subject: [PATCH] feat(graphql): add variables editor for GraphQL requests Closes #576 Adds a JSON variables editor below the query editor for GraphQL requests. Variables are stored as raw JSON text and merged into the request body as a parsed JSON object under the "variables" key, falling back to the raw string when the input is not valid JSON. --- lib/consts.dart | 1 + lib/providers/collection_providers.dart | 2 + .../request_pane/request_body.dart | 50 +++++++++++++++---- .../lib/models/http_request_model.dart | 2 + .../models/http_request_model.freezed.dart | 39 ++++++++------- .../lib/models/http_request_model.g.dart | 2 + .../lib/utils/graphql_utils.dart | 12 ++++- .../test/utils/http_request_utils_test.dart | 38 ++++++++++++++ 8 files changed, 117 insertions(+), 29 deletions(-) diff --git a/lib/consts.dart b/lib/consts.dart index 0159de441c..1575674c6c 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -480,6 +480,7 @@ const kHintContent = "Enter content"; const kHintText = "Enter text"; const kHintJson = "Enter JSON"; const kHintQuery = "Enter Query"; +const kHintVariables = "Enter Variables as JSON"; // TODO: CodeField widget does not allow this hint. To be solved. const kHintScript = "// Use Javascript to modify this request dynamically"; // Response Pane diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index 047b5b09ed..fb9bde6618 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -235,6 +235,7 @@ class CollectionStateNotifier ContentType? bodyContentType, String? body, String? query, + String? variables, List? formData, int? responseStatus, String? message, @@ -296,6 +297,7 @@ class CollectionStateNotifier bodyContentType ?? currentHttpRequestModel.bodyContentType, body: body ?? currentHttpRequestModel.body, query: query ?? currentHttpRequestModel.query, + variables: variables ?? currentHttpRequestModel.variables, formData: formData ?? currentHttpRequestModel.formData, ), responseStatus: responseStatus ?? currentModel.responseStatus, diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart index e6c9826bb6..b0ada3d983 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_body.dart @@ -83,16 +83,46 @@ class EditRequestBody extends ConsumerWidget { APIType.graphql => Expanded( child: Padding( padding: kPt5o10, - child: TextFieldEditor( - key: Key("$selectedId-query"), - fieldKey: "$selectedId-query-editor", - initialValue: requestModel?.httpRequestModel?.query, - onChanged: (String value) { - ref - .read(collectionStateNotifierProvider.notifier) - .update(query: value); - }, - hintText: kHintQuery, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text(kLabelQuery), + kVSpacer5, + Expanded( + flex: 2, + child: TextFieldEditor( + key: Key("$selectedId-query"), + fieldKey: "$selectedId-query-editor", + initialValue: requestModel?.httpRequestModel?.query, + onChanged: (String value) { + ref + .read(collectionStateNotifierProvider.notifier) + .update(query: value); + }, + hintText: kHintQuery, + ), + ), + kVSpacer10, + const Text(kLabelVariables), + kVSpacer5, + Expanded( + flex: 1, + child: JsonTextFieldEditor( + key: Key("$selectedId-graphql-variables"), + fieldKey: + "$selectedId-graphql-variables-editor-$darkMode", + isDark: darkMode, + initialValue: + requestModel?.httpRequestModel?.variables, + onChanged: (String value) { + ref + .read(collectionStateNotifierProvider.notifier) + .update(variables: value); + }, + hintText: kHintVariables, + ), + ), + ], ), ), ), diff --git a/packages/better_networking/lib/models/http_request_model.dart b/packages/better_networking/lib/models/http_request_model.dart index a51b7be9a2..841b4520b5 100644 --- a/packages/better_networking/lib/models/http_request_model.dart +++ b/packages/better_networking/lib/models/http_request_model.dart @@ -25,6 +25,7 @@ abstract class HttpRequestModel with _$HttpRequestModel { @Default(ContentType.json) ContentType bodyContentType, String? body, String? query, + String? variables, List? formData, }) = _HttpRequestModel; @@ -64,6 +65,7 @@ abstract class HttpRequestModel with _$HttpRequestModel { hasFormDataContentType && formDataMapList.isNotEmpty; bool get hasQuery => query?.isNotEmpty ?? false; + bool get hasVariables => variables?.isNotEmpty ?? false; List get formDataList => formData ?? []; List> get formDataMapList => rowsToFormDataMapList(formDataList) ?? []; diff --git a/packages/better_networking/lib/models/http_request_model.freezed.dart b/packages/better_networking/lib/models/http_request_model.freezed.dart index 2e4f6cd044..77c2bb04d1 100644 --- a/packages/better_networking/lib/models/http_request_model.freezed.dart +++ b/packages/better_networking/lib/models/http_request_model.freezed.dart @@ -15,7 +15,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$HttpRequestModel { - HTTPVerb get method; String get url; List? get headers; List? get params; AuthModel? get authModel; List? get isHeaderEnabledList; List? get isParamEnabledList; ContentType get bodyContentType; String? get body; String? get query; List? get formData; + HTTPVerb get method; String get url; List? get headers; List? get params; AuthModel? get authModel; List? get isHeaderEnabledList; List? get isParamEnabledList; ContentType get bodyContentType; String? get body; String? get query; String? get variables; List? get formData; /// Create a copy of HttpRequestModel /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -28,16 +28,16 @@ $HttpRequestModelCopyWith get copyWith => _$HttpRequestModelCo @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is HttpRequestModel&&(identical(other.method, method) || other.method == method)&&(identical(other.url, url) || other.url == url)&&const DeepCollectionEquality().equals(other.headers, headers)&&const DeepCollectionEquality().equals(other.params, params)&&(identical(other.authModel, authModel) || other.authModel == authModel)&&const DeepCollectionEquality().equals(other.isHeaderEnabledList, isHeaderEnabledList)&&const DeepCollectionEquality().equals(other.isParamEnabledList, isParamEnabledList)&&(identical(other.bodyContentType, bodyContentType) || other.bodyContentType == bodyContentType)&&(identical(other.body, body) || other.body == body)&&(identical(other.query, query) || other.query == query)&&const DeepCollectionEquality().equals(other.formData, formData)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is HttpRequestModel&&(identical(other.method, method) || other.method == method)&&(identical(other.url, url) || other.url == url)&&const DeepCollectionEquality().equals(other.headers, headers)&&const DeepCollectionEquality().equals(other.params, params)&&(identical(other.authModel, authModel) || other.authModel == authModel)&&const DeepCollectionEquality().equals(other.isHeaderEnabledList, isHeaderEnabledList)&&const DeepCollectionEquality().equals(other.isParamEnabledList, isParamEnabledList)&&(identical(other.bodyContentType, bodyContentType) || other.bodyContentType == bodyContentType)&&(identical(other.body, body) || other.body == body)&&(identical(other.query, query) || other.query == query)&&(identical(other.variables, variables) || other.variables == variables)&&const DeepCollectionEquality().equals(other.formData, formData)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,method,url,const DeepCollectionEquality().hash(headers),const DeepCollectionEquality().hash(params),authModel,const DeepCollectionEquality().hash(isHeaderEnabledList),const DeepCollectionEquality().hash(isParamEnabledList),bodyContentType,body,query,const DeepCollectionEquality().hash(formData)); +int get hashCode => Object.hash(runtimeType,method,url,const DeepCollectionEquality().hash(headers),const DeepCollectionEquality().hash(params),authModel,const DeepCollectionEquality().hash(isHeaderEnabledList),const DeepCollectionEquality().hash(isParamEnabledList),bodyContentType,body,query,variables,const DeepCollectionEquality().hash(formData)); @override String toString() { - return 'HttpRequestModel(method: $method, url: $url, headers: $headers, params: $params, authModel: $authModel, isHeaderEnabledList: $isHeaderEnabledList, isParamEnabledList: $isParamEnabledList, bodyContentType: $bodyContentType, body: $body, query: $query, formData: $formData)'; + return 'HttpRequestModel(method: $method, url: $url, headers: $headers, params: $params, authModel: $authModel, isHeaderEnabledList: $isHeaderEnabledList, isParamEnabledList: $isParamEnabledList, bodyContentType: $bodyContentType, body: $body, query: $query, variables: $variables, formData: $formData)'; } @@ -48,7 +48,7 @@ abstract mixin class $HttpRequestModelCopyWith<$Res> { factory $HttpRequestModelCopyWith(HttpRequestModel value, $Res Function(HttpRequestModel) _then) = _$HttpRequestModelCopyWithImpl; @useResult $Res call({ - HTTPVerb method, String url, List? headers, List? params, AuthModel? authModel, List? isHeaderEnabledList, List? isParamEnabledList, ContentType bodyContentType, String? body, String? query, List? formData + HTTPVerb method, String url, List? headers, List? params, AuthModel? authModel, List? isHeaderEnabledList, List? isParamEnabledList, ContentType bodyContentType, String? body, String? query, String? variables, List? formData }); @@ -65,7 +65,7 @@ class _$HttpRequestModelCopyWithImpl<$Res> /// Create a copy of HttpRequestModel /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? method = null,Object? url = null,Object? headers = freezed,Object? params = freezed,Object? authModel = freezed,Object? isHeaderEnabledList = freezed,Object? isParamEnabledList = freezed,Object? bodyContentType = null,Object? body = freezed,Object? query = freezed,Object? formData = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? method = null,Object? url = null,Object? headers = freezed,Object? params = freezed,Object? authModel = freezed,Object? isHeaderEnabledList = freezed,Object? isParamEnabledList = freezed,Object? bodyContentType = null,Object? body = freezed,Object? query = freezed,Object? variables = freezed,Object? formData = freezed,}) { return _then(_self.copyWith( method: null == method ? _self.method : method // ignore: cast_nullable_to_non_nullable as HTTPVerb,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable @@ -77,6 +77,7 @@ as List?,isParamEnabledList: freezed == isParamEnabledList ? _self.isParam as List?,bodyContentType: null == bodyContentType ? _self.bodyContentType : bodyContentType // ignore: cast_nullable_to_non_nullable as ContentType,body: freezed == body ? _self.body : body // ignore: cast_nullable_to_non_nullable as String?,query: freezed == query ? _self.query : query // ignore: cast_nullable_to_non_nullable +as String?,variables: freezed == variables ? _self.variables : variables // ignore: cast_nullable_to_non_nullable as String?,formData: freezed == formData ? _self.formData : formData // ignore: cast_nullable_to_non_nullable as List?, )); @@ -175,10 +176,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( HTTPVerb method, String url, List? headers, List? params, AuthModel? authModel, List? isHeaderEnabledList, List? isParamEnabledList, ContentType bodyContentType, String? body, String? query, List? formData)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( HTTPVerb method, String url, List? headers, List? params, AuthModel? authModel, List? isHeaderEnabledList, List? isParamEnabledList, ContentType bodyContentType, String? body, String? query, String? variables, List? formData)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _HttpRequestModel() when $default != null: -return $default(_that.method,_that.url,_that.headers,_that.params,_that.authModel,_that.isHeaderEnabledList,_that.isParamEnabledList,_that.bodyContentType,_that.body,_that.query,_that.formData);case _: +return $default(_that.method,_that.url,_that.headers,_that.params,_that.authModel,_that.isHeaderEnabledList,_that.isParamEnabledList,_that.bodyContentType,_that.body,_that.query,_that.variables,_that.formData);case _: return orElse(); } @@ -196,10 +197,10 @@ return $default(_that.method,_that.url,_that.headers,_that.params,_that.authMode /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( HTTPVerb method, String url, List? headers, List? params, AuthModel? authModel, List? isHeaderEnabledList, List? isParamEnabledList, ContentType bodyContentType, String? body, String? query, List? formData) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( HTTPVerb method, String url, List? headers, List? params, AuthModel? authModel, List? isHeaderEnabledList, List? isParamEnabledList, ContentType bodyContentType, String? body, String? query, String? variables, List? formData) $default,) {final _that = this; switch (_that) { case _HttpRequestModel(): -return $default(_that.method,_that.url,_that.headers,_that.params,_that.authModel,_that.isHeaderEnabledList,_that.isParamEnabledList,_that.bodyContentType,_that.body,_that.query,_that.formData);case _: +return $default(_that.method,_that.url,_that.headers,_that.params,_that.authModel,_that.isHeaderEnabledList,_that.isParamEnabledList,_that.bodyContentType,_that.body,_that.query,_that.variables,_that.formData);case _: throw StateError('Unexpected subclass'); } @@ -216,10 +217,10 @@ return $default(_that.method,_that.url,_that.headers,_that.params,_that.authMode /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( HTTPVerb method, String url, List? headers, List? params, AuthModel? authModel, List? isHeaderEnabledList, List? isParamEnabledList, ContentType bodyContentType, String? body, String? query, List? formData)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( HTTPVerb method, String url, List? headers, List? params, AuthModel? authModel, List? isHeaderEnabledList, List? isParamEnabledList, ContentType bodyContentType, String? body, String? query, String? variables, List? formData)? $default,) {final _that = this; switch (_that) { case _HttpRequestModel() when $default != null: -return $default(_that.method,_that.url,_that.headers,_that.params,_that.authModel,_that.isHeaderEnabledList,_that.isParamEnabledList,_that.bodyContentType,_that.body,_that.query,_that.formData);case _: +return $default(_that.method,_that.url,_that.headers,_that.params,_that.authModel,_that.isHeaderEnabledList,_that.isParamEnabledList,_that.bodyContentType,_that.body,_that.query,_that.variables,_that.formData);case _: return null; } @@ -231,7 +232,7 @@ return $default(_that.method,_that.url,_that.headers,_that.params,_that.authMode @JsonSerializable(explicitToJson: true, anyMap: true) class _HttpRequestModel extends HttpRequestModel { - const _HttpRequestModel({this.method = HTTPVerb.get, this.url = "", final List? headers, final List? params, this.authModel = const AuthModel(type: APIAuthType.none), final List? isHeaderEnabledList, final List? isParamEnabledList, this.bodyContentType = ContentType.json, this.body, this.query, final List? formData}): _headers = headers,_params = params,_isHeaderEnabledList = isHeaderEnabledList,_isParamEnabledList = isParamEnabledList,_formData = formData,super._(); + const _HttpRequestModel({this.method = HTTPVerb.get, this.url = "", final List? headers, final List? params, this.authModel = const AuthModel(type: APIAuthType.none), final List? isHeaderEnabledList, final List? isParamEnabledList, this.bodyContentType = ContentType.json, this.body, this.query, this.variables, final List? formData}): _headers = headers,_params = params,_isHeaderEnabledList = isHeaderEnabledList,_isParamEnabledList = isParamEnabledList,_formData = formData,super._(); factory _HttpRequestModel.fromJson(Map json) => _$HttpRequestModelFromJson(json); @override@JsonKey() final HTTPVerb method; @@ -276,6 +277,7 @@ class _HttpRequestModel extends HttpRequestModel { @override@JsonKey() final ContentType bodyContentType; @override final String? body; @override final String? query; +@override final String? variables; final List? _formData; @override List? get formData { final value = _formData; @@ -299,16 +301,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _HttpRequestModel&&(identical(other.method, method) || other.method == method)&&(identical(other.url, url) || other.url == url)&&const DeepCollectionEquality().equals(other._headers, _headers)&&const DeepCollectionEquality().equals(other._params, _params)&&(identical(other.authModel, authModel) || other.authModel == authModel)&&const DeepCollectionEquality().equals(other._isHeaderEnabledList, _isHeaderEnabledList)&&const DeepCollectionEquality().equals(other._isParamEnabledList, _isParamEnabledList)&&(identical(other.bodyContentType, bodyContentType) || other.bodyContentType == bodyContentType)&&(identical(other.body, body) || other.body == body)&&(identical(other.query, query) || other.query == query)&&const DeepCollectionEquality().equals(other._formData, _formData)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _HttpRequestModel&&(identical(other.method, method) || other.method == method)&&(identical(other.url, url) || other.url == url)&&const DeepCollectionEquality().equals(other._headers, _headers)&&const DeepCollectionEquality().equals(other._params, _params)&&(identical(other.authModel, authModel) || other.authModel == authModel)&&const DeepCollectionEquality().equals(other._isHeaderEnabledList, _isHeaderEnabledList)&&const DeepCollectionEquality().equals(other._isParamEnabledList, _isParamEnabledList)&&(identical(other.bodyContentType, bodyContentType) || other.bodyContentType == bodyContentType)&&(identical(other.body, body) || other.body == body)&&(identical(other.query, query) || other.query == query)&&(identical(other.variables, variables) || other.variables == variables)&&const DeepCollectionEquality().equals(other._formData, _formData)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,method,url,const DeepCollectionEquality().hash(_headers),const DeepCollectionEquality().hash(_params),authModel,const DeepCollectionEquality().hash(_isHeaderEnabledList),const DeepCollectionEquality().hash(_isParamEnabledList),bodyContentType,body,query,const DeepCollectionEquality().hash(_formData)); +int get hashCode => Object.hash(runtimeType,method,url,const DeepCollectionEquality().hash(_headers),const DeepCollectionEquality().hash(_params),authModel,const DeepCollectionEquality().hash(_isHeaderEnabledList),const DeepCollectionEquality().hash(_isParamEnabledList),bodyContentType,body,query,variables,const DeepCollectionEquality().hash(_formData)); @override String toString() { - return 'HttpRequestModel(method: $method, url: $url, headers: $headers, params: $params, authModel: $authModel, isHeaderEnabledList: $isHeaderEnabledList, isParamEnabledList: $isParamEnabledList, bodyContentType: $bodyContentType, body: $body, query: $query, formData: $formData)'; + return 'HttpRequestModel(method: $method, url: $url, headers: $headers, params: $params, authModel: $authModel, isHeaderEnabledList: $isHeaderEnabledList, isParamEnabledList: $isParamEnabledList, bodyContentType: $bodyContentType, body: $body, query: $query, variables: $variables, formData: $formData)'; } @@ -319,7 +321,7 @@ abstract mixin class _$HttpRequestModelCopyWith<$Res> implements $HttpRequestMod factory _$HttpRequestModelCopyWith(_HttpRequestModel value, $Res Function(_HttpRequestModel) _then) = __$HttpRequestModelCopyWithImpl; @override @useResult $Res call({ - HTTPVerb method, String url, List? headers, List? params, AuthModel? authModel, List? isHeaderEnabledList, List? isParamEnabledList, ContentType bodyContentType, String? body, String? query, List? formData + HTTPVerb method, String url, List? headers, List? params, AuthModel? authModel, List? isHeaderEnabledList, List? isParamEnabledList, ContentType bodyContentType, String? body, String? query, String? variables, List? formData }); @@ -336,7 +338,7 @@ class __$HttpRequestModelCopyWithImpl<$Res> /// Create a copy of HttpRequestModel /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? method = null,Object? url = null,Object? headers = freezed,Object? params = freezed,Object? authModel = freezed,Object? isHeaderEnabledList = freezed,Object? isParamEnabledList = freezed,Object? bodyContentType = null,Object? body = freezed,Object? query = freezed,Object? formData = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? method = null,Object? url = null,Object? headers = freezed,Object? params = freezed,Object? authModel = freezed,Object? isHeaderEnabledList = freezed,Object? isParamEnabledList = freezed,Object? bodyContentType = null,Object? body = freezed,Object? query = freezed,Object? variables = freezed,Object? formData = freezed,}) { return _then(_HttpRequestModel( method: null == method ? _self.method : method // ignore: cast_nullable_to_non_nullable as HTTPVerb,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable @@ -348,6 +350,7 @@ as List?,isParamEnabledList: freezed == isParamEnabledList ? _self._isPara as List?,bodyContentType: null == bodyContentType ? _self.bodyContentType : bodyContentType // ignore: cast_nullable_to_non_nullable as ContentType,body: freezed == body ? _self.body : body // ignore: cast_nullable_to_non_nullable as String?,query: freezed == query ? _self.query : query // ignore: cast_nullable_to_non_nullable +as String?,variables: freezed == variables ? _self.variables : variables // ignore: cast_nullable_to_non_nullable as String?,formData: freezed == formData ? _self._formData : formData // ignore: cast_nullable_to_non_nullable as List?, )); diff --git a/packages/better_networking/lib/models/http_request_model.g.dart b/packages/better_networking/lib/models/http_request_model.g.dart index 19a28638ac..b356dc0620 100644 --- a/packages/better_networking/lib/models/http_request_model.g.dart +++ b/packages/better_networking/lib/models/http_request_model.g.dart @@ -30,6 +30,7 @@ _HttpRequestModel _$HttpRequestModelFromJson(Map json) => _HttpRequestModel( ContentType.json, body: json['body'] as String?, query: json['query'] as String?, + variables: json['variables'] as String?, formData: (json['formData'] as List?) ?.map((e) => FormDataModel.fromJson(Map.from(e as Map))) .toList(), @@ -47,6 +48,7 @@ Map _$HttpRequestModelToJson(_HttpRequestModel instance) => 'bodyContentType': _$ContentTypeEnumMap[instance.bodyContentType]!, 'body': instance.body, 'query': instance.query, + 'variables': instance.variables, 'formData': instance.formData?.map((e) => e.toJson()).toList(), }; diff --git a/packages/better_networking/lib/utils/graphql_utils.dart b/packages/better_networking/lib/utils/graphql_utils.dart index e7d982d1ce..342c347abd 100644 --- a/packages/better_networking/lib/utils/graphql_utils.dart +++ b/packages/better_networking/lib/utils/graphql_utils.dart @@ -1,9 +1,19 @@ +import 'dart:convert'; import '../consts.dart'; import '../models/models.dart'; String? getGraphQLBody(HttpRequestModel httpRequestModel) { if (httpRequestModel.hasQuery) { - return kJsonEncoder.convert({"query": httpRequestModel.query}); + final Map graphqlBody = {"query": httpRequestModel.query}; + if (httpRequestModel.hasVariables) { + try { + graphqlBody["variables"] = jsonDecode(httpRequestModel.variables!); + } catch (_) { + // Keep the raw text if it isn't valid JSON so nothing is lost. + graphqlBody["variables"] = httpRequestModel.variables; + } + } + return kJsonEncoder.convert(graphqlBody); } return null; } diff --git a/packages/better_networking/test/utils/http_request_utils_test.dart b/packages/better_networking/test/utils/http_request_utils_test.dart index 298e874989..eb181043a3 100644 --- a/packages/better_networking/test/utils/http_request_utils_test.dart +++ b/packages/better_networking/test/utils/http_request_utils_test.dart @@ -241,6 +241,44 @@ void main() { final result = getRequestBody(APIType.graphql, model); expect(result, isNull); }); + + test('Includes parsed variables as JSON object when present', () { + const model = HttpRequestModel( + query: r'query($id: ID!) { user(id: $id) { name } }', + variables: '{"id": "42"}', + method: HTTPVerb.post, + ); + final result = getRequestBody(APIType.graphql, model); + expect( + result, + '{\n "query": "query(\$id: ID!) { user(id: \$id) { name } }",\n' + ' "variables": {\n "id": "42"\n }\n}', + ); + }); + + test('Omits variables key for GraphQL when variables are empty', () { + const model = HttpRequestModel( + query: '{ users { name } }', + variables: '', + method: HTTPVerb.post, + ); + final result = getRequestBody(APIType.graphql, model); + expect(result, '{\n "query": "{ users { name } }"\n}'); + }); + + test('Falls back to raw variables string when JSON is invalid', () { + const model = HttpRequestModel( + query: '{ users { name } }', + variables: 'not valid json', + method: HTTPVerb.post, + ); + final result = getRequestBody(APIType.graphql, model); + expect( + result, + '{\n "query": "{ users { name } }",\n' + ' "variables": "not valid json"\n}', + ); + }); }); group('getFormDataType', () {