From 18f691bded253d45029f1b7969a0f2a94adbd78e Mon Sep 17 00:00:00 2001 From: Nicolas Mouginot Date: Fri, 14 Nov 2025 14:06:41 +0100 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9A=B0=EF=B8=8FRemove=20every=20'.fromJs?= =?UTF-8?q?on()'=20function,=20we=20are=20building=20Query=20from=20code?= =?UTF-8?q?=20to=20json,=20we=20don't=20need=20to=20reverse=20of=20it.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../body/laravel_rest_api_actions_body.dart | 33 +---- .../body/laravel_rest_api_mutate_body.dart | 31 +--- .../body/laravel_rest_api_search_body.dart | 132 +----------------- .../laravel_rest_api_mutate_reponse.dart | 17 +-- .../laravel_rest_api_search_response.dart | 59 +------- 5 files changed, 18 insertions(+), 254 deletions(-) diff --git a/lib/data/core/models/laravel_rest_api/body/laravel_rest_api_actions_body.dart b/lib/data/core/models/laravel_rest_api/body/laravel_rest_api_actions_body.dart index 6bcdfe3..95bee74 100644 --- a/lib/data/core/models/laravel_rest_api/body/laravel_rest_api_actions_body.dart +++ b/lib/data/core/models/laravel_rest_api/body/laravel_rest_api_actions_body.dart @@ -1,22 +1,10 @@ class LaravelRestApiActionsBody { final List fields; - LaravelRestApiActionsBody({ - required this.fields, - }); - - factory LaravelRestApiActionsBody.fromJson(Map json) { - return LaravelRestApiActionsBody( - fields: (json['fields'] as List) - .map((e) => Action.fromJson(e as Map)) - .toList(), - ); - } + LaravelRestApiActionsBody({required this.fields}); Map toJson() { - return { - 'fields': fields.map((action) => action.toJson()).toList(), - }; + return {'fields': fields.map((action) => action.toJson()).toList()}; } } @@ -24,22 +12,9 @@ class Action { final String name; final String value; - Action({ - required this.name, - required this.value, - }); - - factory Action.fromJson(Map json) { - return Action( - name: json['name'] as String, - value: json['value'] as String, - ); - } + Action({required this.name, required this.value}); Map toJson() { - return { - 'name': name, - 'value': value, - }; + return {'name': name, 'value': value}; } } diff --git a/lib/data/core/models/laravel_rest_api/body/laravel_rest_api_mutate_body.dart b/lib/data/core/models/laravel_rest_api/body/laravel_rest_api_mutate_body.dart index cbb6cf2..15251f5 100644 --- a/lib/data/core/models/laravel_rest_api/body/laravel_rest_api_mutate_body.dart +++ b/lib/data/core/models/laravel_rest_api/body/laravel_rest_api_mutate_body.dart @@ -1,13 +1,7 @@ class LaravelRestApiMutateBody { List mutate; - LaravelRestApiMutateBody({ - required this.mutate, - }); - - factory LaravelRestApiMutateBody.fromJson(Map json) { - return LaravelRestApiMutateBody(mutate: json["mutate"]); - } + LaravelRestApiMutateBody({required this.mutate}); Map toJson() { return {"mutate": mutate.map((m) => m.toJson()).toList()}; @@ -19,19 +13,7 @@ class Mutation { Map? attributes; dynamic key; - Mutation({ - required this.operation, - this.attributes, - this.key, - }); - - factory Mutation.fromJson(Map json) { - return Mutation( - operation: MutationOperation.values.byName(json['operation']), - attributes: json['attributes'], - key: json['key'], - ); - } + Mutation({required this.operation, this.attributes, this.key}); Map toJson() { return { @@ -42,11 +24,4 @@ class Mutation { } } -enum MutationOperation { - create, - update, - attach, - detach, - toggle, - sync, -} +enum MutationOperation { create, update, attach, detach, toggle, sync } diff --git a/lib/data/core/models/laravel_rest_api/body/laravel_rest_api_search_body.dart b/lib/data/core/models/laravel_rest_api/body/laravel_rest_api_search_body.dart index c5b93f0..783d3ca 100644 --- a/lib/data/core/models/laravel_rest_api/body/laravel_rest_api_search_body.dart +++ b/lib/data/core/models/laravel_rest_api/body/laravel_rest_api_search_body.dart @@ -25,37 +25,6 @@ class LaravelRestApiSearchBody { this.limit, }); - factory LaravelRestApiSearchBody.fromJson(Map json) { - return LaravelRestApiSearchBody( - text: json['text'] != null ? TextSearch.fromJson(json['text']) : null, - scopes: (json['scopes'] as List?) - ?.map((e) => Scope.fromJson(e)) - .toList(), - filters: (json['filters'] as List?) - ?.map((e) => Filter.fromJson(e)) - .toList(), - sorts: (json['sorts'] as List?) - ?.map((e) => Sort.fromJson(e)) - .toList(), - selects: (json['selects'] as List?) - ?.map((e) => Select.fromJson(e)) - .toList(), - includes: (json['includes'] as List?) - ?.map((e) => Include.fromJson(e)) - .toList(), - aggregates: (json['aggregates'] as List?) - ?.map((e) => Aggregate.fromJson(e)) - .toList(), - instructions: (json['instructions'] as List?) - ?.map((e) => Instruction.fromJson(e)) - .toList(), - gates: - (json['gates'] as List?)?.map((e) => e as String).toList(), - page: json['page'] as int?, - limit: json['limit'] as int?, - ); - } - Map toJson() { return { if (text != null) 'text': text!.toJson(), @@ -81,14 +50,8 @@ class TextSearch { TextSearch({this.value}); - factory TextSearch.fromJson(Map json) { - return TextSearch(value: json['value'] as String?); - } - Map toJson() { - return { - if (value != null) 'value': value, - }; + return {if (value != null) 'value': value}; } } @@ -98,18 +61,8 @@ class Scope { Scope({required this.name, this.parameters}); - factory Scope.fromJson(Map json) { - return Scope( - name: json['name'] as String, - parameters: json['parameters'] as List?, - ); - } - Map toJson() { - return { - 'name': name, - if (parameters != null) 'parameters': parameters, - }; + return {'name': name, if (parameters != null) 'parameters': parameters}; } } @@ -122,18 +75,6 @@ class Filter { Filter({this.field, this.operator, this.value, this.type, this.nested}); - factory Filter.fromJson(Map json) { - return Filter( - field: json['field'] as String?, - operator: json['operator'] as String?, - value: json['value'], - type: json['type'] as String?, - nested: (json['nested'] as List?) - ?.map((e) => Filter.fromJson(e)) - .toList(), - ); - } - Map toJson() { return { if (field != null) 'field': field, @@ -151,18 +92,8 @@ class Sort { Sort({required this.field, required this.direction}); - factory Sort.fromJson(Map json) { - return Sort( - field: json['field'] as String, - direction: json['direction'] as String, - ); - } - Map toJson() { - return { - 'field': field, - 'direction': direction, - }; + return {'field': field, 'direction': direction}; } } @@ -171,16 +102,8 @@ class Select { Select({required this.field}); - factory Select.fromJson(Map json) { - return Select( - field: json['field'] as String, - ); - } - Map toJson() { - return { - 'field': field, - }; + return {'field': field}; } } @@ -191,16 +114,6 @@ class Include { Include({required this.relation, this.filters, this.limit}); - factory Include.fromJson(Map json) { - return Include( - relation: json['relation'] as String, - filters: (json['filters'] as List?) - ?.map((e) => Filter.fromJson(e)) - .toList(), - limit: json['limit'] as int?, - ); - } - Map toJson() { return { 'relation': relation, @@ -223,17 +136,6 @@ class Aggregate { this.filters, }); - factory Aggregate.fromJson(Map json) { - return Aggregate( - relation: json['relation'] as String, - type: json['type'] as String, - field: json['field'] as String, - filters: (json['filters'] as List?) - ?.map((e) => Filter.fromJson(e)) - .toList(), - ); - } - Map toJson() { return { 'relation': relation, @@ -250,20 +152,8 @@ class Instruction { Instruction({required this.name, required this.fields}); - factory Instruction.fromJson(Map json) { - return Instruction( - name: json['name'] as String, - fields: (json['fields'] as List) - .map((e) => Field.fromJson(e)) - .toList(), - ); - } - Map toJson() { - return { - 'name': name, - 'fields': fields.map((e) => e.toJson()).toList(), - }; + return {'name': name, 'fields': fields.map((e) => e.toJson()).toList()}; } } @@ -273,17 +163,7 @@ class Field { Field({required this.name, required this.value}); - factory Field.fromJson(Map json) { - return Field( - name: json['name'] as String, - value: json['value'], - ); - } - Map toJson() { - return { - 'name': name, - 'value': value, - }; + return {'name': name, 'value': value}; } } diff --git a/lib/data/core/models/laravel_rest_api/response/laravel_rest_api_mutate_reponse.dart b/lib/data/core/models/laravel_rest_api/response/laravel_rest_api_mutate_reponse.dart index b2cf869..d5fddbb 100644 --- a/lib/data/core/models/laravel_rest_api/response/laravel_rest_api_mutate_reponse.dart +++ b/lib/data/core/models/laravel_rest_api/response/laravel_rest_api_mutate_reponse.dart @@ -2,22 +2,9 @@ class LaravelRestApiMutateResponse { final List created; final List updated; - LaravelRestApiMutateResponse({ - required this.created, - required this.updated, - }); - - factory LaravelRestApiMutateResponse.fromJson(Map json) { - return LaravelRestApiMutateResponse( - created: List.from(json['created'] ?? []), - updated: List.from(json['updated'] ?? []), - ); - } + LaravelRestApiMutateResponse({required this.created, required this.updated}); Map toJson() { - return { - 'created': created, - 'updated': updated, - }; + return {'created': created, 'updated': updated}; } } diff --git a/lib/data/core/models/laravel_rest_api/response/laravel_rest_api_search_response.dart b/lib/data/core/models/laravel_rest_api/response/laravel_rest_api_search_response.dart index c995f78..d943af1 100644 --- a/lib/data/core/models/laravel_rest_api/response/laravel_rest_api_search_response.dart +++ b/lib/data/core/models/laravel_rest_api/response/laravel_rest_api_search_response.dart @@ -19,21 +19,6 @@ class PaginatedResponse { required this.meta, }); - factory PaginatedResponse.fromJson(Map json) { - return PaginatedResponse( - currentPage: json['current_page'], - data: (json['data'] as List) - .map((item) => UserData.fromJson(item)) - .toList(), - from: json['from'], - lastPage: json['last_page'], - perPage: json['per_page'], - to: json['to'], - total: json['total'], - meta: MetaData.fromJson(json['meta']), - ); - } - Map toJson() { return { 'current_page': currentPage, @@ -55,20 +40,8 @@ class UserData { UserData({required this.id, required this.name, required this.gates}); - factory UserData.fromJson(Map json) { - return UserData( - id: json['id'], - name: json['name'], - gates: Gates.fromJson(json['gates']), - ); - } - Map toJson() { - return { - 'id': id, - 'name': name, - 'gates': gates.toJson(), - }; + return {'id': id, 'name': name, 'gates': gates.toJson()}; } } @@ -87,16 +60,6 @@ class Gates { required this.authorizedToForceDelete, }); - factory Gates.fromJson(Map json) { - return Gates( - authorizedToView: json['authorized_to_view'], - authorizedToUpdate: json['authorized_to_update'], - authorizedToDelete: json['authorized_to_delete'], - authorizedToRestore: json['authorized_to_restore'], - authorizedToForceDelete: json['authorized_to_force_delete'], - ); - } - Map toJson() { return { 'authorized_to_view': authorizedToView, @@ -113,16 +76,8 @@ class MetaData { MetaData({required this.gates}); - factory MetaData.fromJson(Map json) { - return MetaData( - gates: MetaGates.fromJson(json['gates']), - ); - } - Map toJson() { - return { - 'gates': gates.toJson(), - }; + return {'gates': gates.toJson()}; } } @@ -131,15 +86,7 @@ class MetaGates { MetaGates({required this.authorizedToCreate}); - factory MetaGates.fromJson(Map json) { - return MetaGates( - authorizedToCreate: json['authorized_to_create'], - ); - } - Map toJson() { - return { - 'authorized_to_create': authorizedToCreate, - }; + return {'authorized_to_create': authorizedToCreate}; } } From a8181408f079661673dcf180ec2278cb2e3e2580 Mon Sep 17 00:00:00 2001 From: Nicolas Mouginot Date: Fri, 14 Nov 2025 14:07:34 +0100 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove=20the=20generat?= =?UTF-8?q?ed=20example=20test=20folder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/test/widget_test.dart | 1 - 1 file changed, 1 deletion(-) delete mode 100644 example/test/widget_test.dart diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart deleted file mode 100644 index ab73b3a..0000000 --- a/example/test/widget_test.dart +++ /dev/null @@ -1 +0,0 @@ -void main() {} From 0c057daea1065ee89efd0ac56d46d724dff7e3ac Mon Sep 17 00:00:00 2001 From: Nicolas Mouginot Date: Fri, 14 Nov 2025 14:17:48 +0100 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=8E=A8=20Format=20the=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/core/constants.dart | 2 +- .../laravel_rest_api_actions_factory.dart | 5 +- .../laravel_rest_api_delete_factory.dart | 11 +- .../laravel_rest_api_search_factory.dart | 9 +- lib/data/core/rest_api_repository.dart | 12 +- .../core/rest_api_testing_interceptor.dart | 4 +- .../rest_api_client/api_http_client.dart.dart | 36 ++-- test/actions_factory_mock_test.dart | 58 +++---- test/delete_factory_mock_test.dart | 46 +++--- test/mock/item_model.dart | 10 +- test/mock/mock_http_client.dart | 17 +- test/mutate_factory_mock_test.dart | 94 ++++++----- test/search_factory_mock_test.dart | 156 +++++++++--------- 13 files changed, 221 insertions(+), 239 deletions(-) diff --git a/lib/data/core/constants.dart b/lib/data/core/constants.dart index 43e07b0..9cd3b3e 100644 --- a/lib/data/core/constants.dart +++ b/lib/data/core/constants.dart @@ -8,5 +8,5 @@ const List successStatus = [ 206, 207, 208, - 226 + 226, ]; diff --git a/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_actions_factory.dart b/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_actions_factory.dart index d7dadd5..fe52a5a 100644 --- a/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_actions_factory.dart +++ b/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_actions_factory.dart @@ -32,10 +32,7 @@ mixin ActionsFactory { data: response.body?['data']['impacted'] ?? 0, ); } catch (exception) { - return RestApiResponse( - message: response.message, - statusCode: 500, - ); + return RestApiResponse(message: response.message, statusCode: 500); } } } diff --git a/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_delete_factory.dart b/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_delete_factory.dart index b0fd2b0..64b87c8 100644 --- a/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_delete_factory.dart +++ b/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_delete_factory.dart @@ -22,14 +22,13 @@ mixin DeleteFactory { baseRoute, apiMethod: ApiMethod.delete, client: httpClient, - body: { - "resources": resourceIds, - }, + body: {"resources": resourceIds}, ); - final items = (response.body?['data'] as List) - .map((item) => fromJson(item)) - .toList(); + final items = + (response.body?['data'] as List) + .map((item) => fromJson(item)) + .toList(); return RestApiResponse>( statusCode: response.statusCode, body: response.body, diff --git a/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_search_factory.dart b/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_search_factory.dart index 0ad0399..0b35dd3 100644 --- a/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_search_factory.dart +++ b/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_search_factory.dart @@ -84,7 +84,7 @@ mixin SearchFactory { 'instructions': instructions.map((e) => e.toJson()).toList(), if (limit != null) 'limit': limit, if (instructions != null) 'page': page, - } + }, }; // Sending the request using a REST API client. @@ -117,9 +117,10 @@ mixin SearchFactory { ); } try { - final items = (response.body?['data'] as List) - .map((item) => fromJson(item)) - .toList(); + final items = + (response.body?['data'] as List) + .map((item) => fromJson(item)) + .toList(); return RestApiResponse>( data: items, body: response.body, diff --git a/lib/data/core/rest_api_repository.dart b/lib/data/core/rest_api_repository.dart index 8d319ea..fbd3b95 100644 --- a/lib/data/core/rest_api_repository.dart +++ b/lib/data/core/rest_api_repository.dart @@ -24,17 +24,9 @@ Future handlingResponse( case ApiMethod.get: response = await client.get(route, headers: headers); case ApiMethod.post: - response = await client.post( - route, - body: body, - headers: headers, - ); + response = await client.post(route, body: body, headers: headers); case ApiMethod.delete: - response = await client.delete( - route, - body: body, - headers: headers, - ); + response = await client.delete(route, body: body, headers: headers); } if (successStatus.contains(response.statusCode)) { diff --git a/lib/data/core/rest_api_testing_interceptor.dart b/lib/data/core/rest_api_testing_interceptor.dart index fbadb59..18b3cd9 100644 --- a/lib/data/core/rest_api_testing_interceptor.dart +++ b/lib/data/core/rest_api_testing_interceptor.dart @@ -5,7 +5,9 @@ class RestApiTestingInterceptor extends Interceptor { @override void onRequest( - RequestOptions options, RequestInterceptorHandler handler) async { + RequestOptions options, + RequestInterceptorHandler handler, + ) async { // await dotenv.load(fileName: ".env/.env.testing"); // options.headers["Authorization"] = dotenv.env['TESTING_JWT']; return super.onRequest(options, handler); diff --git a/lib/data/rest_api_client/api_http_client.dart.dart b/lib/data/rest_api_client/api_http_client.dart.dart index 0e3ddf8..996d6a1 100644 --- a/lib/data/rest_api_client/api_http_client.dart.dart +++ b/lib/data/rest_api_client/api_http_client.dart.dart @@ -7,11 +7,17 @@ class ApiHttpClient implements RestApiClient { ApiHttpClient({required this.dio}); @override - Future get(String url, - {Map? headers, Map? queryParams}) async { + Future get( + String url, { + Map? headers, + Map? queryParams, + }) async { try { - final response = await dio.get("${dio.options.baseUrl}$url", - options: Options(headers: headers), queryParameters: queryParams); + final response = await dio.get( + "${dio.options.baseUrl}$url", + options: Options(headers: headers), + queryParameters: queryParams, + ); return RestApiResponse( statusCode: response.statusCode ?? 500, body: response.data, @@ -29,14 +35,17 @@ class ApiHttpClient implements RestApiClient { Object? body, }) async { try { - final response = await dio.post("${dio.options.baseUrl}$url", - options: Options(headers: headers), - queryParameters: queryParams, - data: body); + final response = await dio.post( + "${dio.options.baseUrl}$url", + options: Options(headers: headers), + queryParameters: queryParams, + data: body, + ); return RestApiResponse( - statusCode: response.statusCode ?? 500, - body: response.data, - message: response.statusMessage); + statusCode: response.statusCode ?? 500, + body: response.data, + message: response.statusMessage, + ); } catch (exception, stackTrace) { return handleError(exception, stackTrace); } @@ -73,9 +82,6 @@ class ApiHttpClient implements RestApiClient { body: exception.response?.data ?? {'error': exception.message}, ); } - return RestApiResponse( - statusCode: 500, - body: {'error': exception}, - ); + return RestApiResponse(statusCode: 500, body: {'error': exception}); } } diff --git a/test/actions_factory_mock_test.dart b/test/actions_factory_mock_test.dart index f6c6cbc..3a299b9 100644 --- a/test/actions_factory_mock_test.dart +++ b/test/actions_factory_mock_test.dart @@ -46,19 +46,14 @@ void main() { requestOptions: RequestOptions(), statusCode: 200, data: { - "data": {"impacted": 10} + "data": {"impacted": 10}, }, ), ); final result = await ItemRepository(mockDio).actions( data: LaravelRestApiActionsBody( - fields: [ - Action( - name: "expires_at", - value: "2023-04-29", - ), - ], + fields: [Action(name: "expires_at", value: "2023-04-29")], ), ); @@ -66,34 +61,33 @@ void main() { }); test('[500] With common laravel error message', () async { - when(mockDio.post( - '/items', - data: { - "fields": [ - {"name": "expires_at", "value": "2023-04-29"}, - ], - }, - )).thenAnswer((_) async => Response( - requestOptions: RequestOptions(), - statusCode: 500, - data: { - "message": "Server error", - "exception": - "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException", - "file": - "/path/to/project/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php", - "line": 23 - }, - )); + when( + mockDio.post( + '/items', + data: { + "fields": [ + {"name": "expires_at", "value": "2023-04-29"}, + ], + }, + ), + ).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(), + statusCode: 500, + data: { + "message": "Server error", + "exception": + "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException", + "file": + "/path/to/project/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php", + "line": 23, + }, + ), + ); final result = await ItemRepository(mockDio).actions( data: LaravelRestApiActionsBody( - fields: [ - Action( - name: "expires_at", - value: "2023-04-29", - ), - ], + fields: [Action(name: "expires_at", value: "2023-04-29")], ), ); diff --git a/test/delete_factory_mock_test.dart b/test/delete_factory_mock_test.dart index bc6662e..cca87cc 100644 --- a/test/delete_factory_mock_test.dart +++ b/test/delete_factory_mock_test.dart @@ -35,7 +35,7 @@ void main() { mockDio.delete( '/items', data: { - "resources": [5, 6] + "resources": [5, 6], }, ), ).thenAnswer( @@ -45,37 +45,39 @@ void main() { data: { "data": [ {"id": 5, "name": "Axe"}, - {"id": 6, "name": "Hammer"} + {"id": 6, "name": "Hammer"}, ], }, ), ); - final result = await ItemRepository(mockDio).delete( - resourceIds: [5, 6], - ); + final result = await ItemRepository(mockDio).delete(resourceIds: [5, 6]); expect(result.data?.length, 2); }); test('[500] With common laravel error message', () async { - when(mockDio.delete( - '/items', - data: { - "resources": [5, 6] - }, - )).thenAnswer((_) async => Response( - requestOptions: RequestOptions(), - statusCode: 500, - data: { - "message": "Server error", - "exception": - "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException", - "file": - "/path/to/project/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php", - "line": 23 - }, - )); + when( + mockDio.delete( + '/items', + data: { + "resources": [5, 6], + }, + ), + ).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(), + statusCode: 500, + data: { + "message": "Server error", + "exception": + "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException", + "file": + "/path/to/project/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php", + "line": 23, + }, + ), + ); final result = await ItemRepository(mockDio).delete(resourceIds: [5, 6]); diff --git a/test/mock/item_model.dart b/test/mock/item_model.dart index c66fe40..f7880b3 100644 --- a/test/mock/item_model.dart +++ b/test/mock/item_model.dart @@ -5,16 +5,10 @@ class ItemModel { ItemModel({required this.id, required this.name}); Map toJson() { - return { - 'id': id, - 'name': name, - }; + return {'id': id, 'name': name}; } factory ItemModel.fromJson(Map json) { - return ItemModel( - id: json['id'] as int, - name: json['name'] as String, - ); + return ItemModel(id: json['id'] as int, name: json['name'] as String); } } diff --git a/test/mock/mock_http_client.dart b/test/mock/mock_http_client.dart index 2e4754e..b98d2a5 100644 --- a/test/mock/mock_http_client.dart +++ b/test/mock/mock_http_client.dart @@ -9,8 +9,11 @@ class MockApiHttpClient implements RestApiClient { MockApiHttpClient({required this.dio}); @override - Future get(String url, - {Map? headers, Map? queryParams}) async { + Future get( + String url, { + Map? headers, + Map? queryParams, + }) async { try { final response = await dio.get( url, @@ -52,10 +55,7 @@ class MockApiHttpClient implements RestApiClient { Object? body, }) async { try { - final response = await dio.delete( - url, - data: body, - ); + final response = await dio.delete(url, data: body); return RestApiResponse( statusCode: response.statusCode, body: response.data, @@ -73,9 +73,6 @@ class MockApiHttpClient implements RestApiClient { body: exception.response?.data ?? {'error': exception.message}, ); } - return RestApiResponse( - statusCode: 500, - body: {'error': exception}, - ); + return RestApiResponse(statusCode: 500, body: {'error': exception}); } } diff --git a/test/mutate_factory_mock_test.dart b/test/mutate_factory_mock_test.dart index 06afeac..c067866 100644 --- a/test/mutate_factory_mock_test.dart +++ b/test/mutate_factory_mock_test.dart @@ -41,7 +41,7 @@ void main() { "operation": "create", "attributes": ItemModel(id: 1, name: "name").toJson(), }, - ] + ], }, ), ).thenAnswer( @@ -50,7 +50,7 @@ void main() { statusCode: 200, data: { "created": [1], - "updated": [] + "updated": [], }, ), ); @@ -61,7 +61,7 @@ void main() { Mutation( operation: MutationOperation.create, attributes: ItemModel(id: 1, name: "name").toJson(), - ) + ), ], ), ); @@ -73,28 +73,32 @@ void main() { }); test('[500] With common laravel error message', () async { - when(mockDio.post( - '/items/mutate', - data: { - "mutate": [ - { - "operation": "create", - "attributes": ItemModel(id: 1, name: "name").toJson(), - }, - ] - }, - )).thenAnswer((_) async => Response( - requestOptions: RequestOptions(), - statusCode: 500, - data: { - "message": "Server error", - "exception": - "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException", - "file": - "/path/to/project/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php", - "line": 23 - }, - )); + when( + mockDio.post( + '/items/mutate', + data: { + "mutate": [ + { + "operation": "create", + "attributes": ItemModel(id: 1, name: "name").toJson(), + }, + ], + }, + ), + ).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(), + statusCode: 500, + data: { + "message": "Server error", + "exception": + "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException", + "file": + "/path/to/project/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php", + "line": 23, + }, + ), + ); final result = await ItemRepository(mockDio).mutate( body: LaravelRestApiMutateBody( @@ -102,7 +106,7 @@ void main() { Mutation( operation: MutationOperation.create, attributes: ItemModel(id: 1, name: "name").toJson(), - ) + ), ], ), ); @@ -112,23 +116,25 @@ void main() { }); }); test('[500] With custom object error message returned', () async { - when(mockDio.post( - '/items/mutate', - data: { - "mutate": [ - { - "operation": "create", - "attributes": ItemModel(id: 1, name: "name").toJson(), - }, - ] - }, - )).thenAnswer((_) async => Response( - requestOptions: RequestOptions(), - statusCode: 500, - data: { - "error": "error", - }, - )); + when( + mockDio.post( + '/items/mutate', + data: { + "mutate": [ + { + "operation": "create", + "attributes": ItemModel(id: 1, name: "name").toJson(), + }, + ], + }, + ), + ).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(), + statusCode: 500, + data: {"error": "error"}, + ), + ); final result = await ItemRepository(mockDio).mutate( body: LaravelRestApiMutateBody( @@ -136,7 +142,7 @@ void main() { Mutation( operation: MutationOperation.create, attributes: ItemModel(id: 1, name: "name").toJson(), - ) + ), ], ), ); diff --git a/test/search_factory_mock_test.dart b/test/search_factory_mock_test.dart index afce78e..e6da616 100644 --- a/test/search_factory_mock_test.dart +++ b/test/search_factory_mock_test.dart @@ -42,7 +42,8 @@ class ItemRepositoryWithDefaultBody with SearchFactory { @override LaravelRestApiSearchBody? get defaultSearchBody => LaravelRestApiSearchBody( - filters: [Filter(field: "field", operator: "operator", value: "value")]); + filters: [Filter(field: "field", operator: "operator", value: "value")], + ); @override ItemModel fromJson(Map item) => ItemModel.fromJson(item); @@ -64,16 +65,18 @@ void main() { group('Search Factory Tests', () { test('[200] Successful API call with valid JSON', () async { - when(mockDio.post('/items/search')).thenAnswer((_) async => Response( - requestOptions: RequestOptions(), - statusCode: 200, - data: { - 'data': [ - {'id': 1, 'name': 'Lou West'}, - {'id': 2, 'name': 'Bridget Wilderman'}, - ], - }, - )); + when(mockDio.post('/items/search')).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(), + statusCode: 200, + data: { + 'data': [ + {'id': 1, 'name': 'Lou West'}, + {'id': 2, 'name': 'Bridget Wilderman'}, + ], + }, + ), + ); final result = await ItemRepository(mockDio).search(); @@ -82,18 +85,18 @@ void main() { }); test('[200] Successful API call with bad JSON', () async { - when(mockDio.post( - '/items/search', - )).thenAnswer((_) async => Response( - requestOptions: RequestOptions(), - statusCode: 200, - data: { - 'data': [ - {'idd': 1, 'name': 'Lou West'}, - {'idd': 2, 'name': 'Bridget Wilderman'}, - ], - }, - )); + when(mockDio.post('/items/search')).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(), + statusCode: 200, + data: { + 'data': [ + {'idd': 1, 'name': 'Lou West'}, + {'idd': 2, 'name': 'Bridget Wilderman'}, + ], + }, + ), + ); final result = await ItemRepository(mockDio).search(); @@ -102,20 +105,20 @@ void main() { }); test('[404] With common laravel error message', () async { - when(mockDio.post( - '/items/search', - )).thenAnswer((_) async => Response( - requestOptions: RequestOptions(), - statusCode: 404, - data: { - "message": "Not Found", - "exception": - "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException", - "file": - "/path/to/project/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php", - "line": 23 - }, - )); + when(mockDio.post('/items/search')).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(), + statusCode: 404, + data: { + "message": "Not Found", + "exception": + "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException", + "file": + "/path/to/project/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php", + "line": 23, + }, + ), + ); final result = await ItemRepository(mockDio).search(); @@ -124,15 +127,13 @@ void main() { }); }); test('[500] With custom object error message returned', () async { - when(mockDio.post( - '/items/search', - )).thenAnswer((_) async => Response( - requestOptions: RequestOptions(), - statusCode: 500, - data: { - "error": "error", - }, - )); + when(mockDio.post('/items/search')).thenAnswer( + (_) async => Response( + requestOptions: RequestOptions(), + statusCode: 500, + data: {"error": "error"}, + ), + ); final result = await ItemRepository(mockDio).search(); @@ -141,16 +142,12 @@ void main() { }); test('[500] With custom list error message returned', () async { - when(mockDio.post( - '/items/search', - )).thenAnswer( + when(mockDio.post('/items/search')).thenAnswer( (_) async => Response( requestOptions: RequestOptions(), statusCode: 500, data: [ - { - "error": "error", - } + {"error": "error"}, ], ), ); @@ -162,24 +159,21 @@ void main() { }); test('Check if all attributes filter can be send in body', () async { - when(mockDio.post( - '/items/search', - data: anyNamed('data'), - )).thenAnswer((_) async => Response( - requestOptions: RequestOptions(), - statusCode: 200, - )); + when(mockDio.post('/items/search', data: anyNamed('data'))).thenAnswer( + (_) async => Response(requestOptions: RequestOptions(), statusCode: 200), + ); await ItemRepositoryWithDefaultBody(mockDio).search( - filters: [ - Filter(field: "field", type: "type"), - ], + filters: [Filter(field: "field", type: "type")], aggregates: [ - Aggregate(relation: "relation", type: "type", field: "field") + Aggregate(relation: "relation", type: "type", field: "field"), ], includes: [Include(relation: "relation")], instructions: [ - Instruction(name: "name", fields: [Field(name: "name", value: "value")]) + Instruction( + name: "name", + fields: [Field(name: "name", value: "value")], + ), ], limit: 1, page: 1, @@ -189,10 +183,10 @@ void main() { ); // Check body send to api - final capturedArgs = verify(mockDio.post( - '/items/search', - data: captureAnyNamed('data'), - )).captured; + final capturedArgs = + verify( + mockDio.post('/items/search', data: captureAnyNamed('data')), + ).captured; expect(capturedArgs[0].containsKey('search'), isTrue); expect(capturedArgs[0]["search"].containsKey('filters'), isTrue); @@ -206,23 +200,21 @@ void main() { expect(capturedArgs[0]["search"].containsKey('page'), isTrue); }); test('Check if defaultSearchBody is correctly send to api', () async { - when(mockDio.post( - '/items/search', - data: anyNamed('data'), - )).thenAnswer((_) async => Response( - requestOptions: RequestOptions(), - statusCode: 200, - )); + when(mockDio.post('/items/search', data: anyNamed('data'))).thenAnswer( + (_) async => Response(requestOptions: RequestOptions(), statusCode: 200), + ); - await ItemRepositoryWithDefaultBody(mockDio).search(aggregates: [ - Aggregate(relation: "relation", type: "type", field: "field") - ]); + await ItemRepositoryWithDefaultBody(mockDio).search( + aggregates: [ + Aggregate(relation: "relation", type: "type", field: "field"), + ], + ); // Check body send to api - final capturedArgs = verify(mockDio.post( - '/items/search', - data: captureAnyNamed('data'), - )).captured; + final capturedArgs = + verify( + mockDio.post('/items/search', data: captureAnyNamed('data')), + ).captured; expect(capturedArgs[0].containsKey('search'), isTrue); expect(capturedArgs[0]["search"].containsKey('filters'), isTrue); From ff3cd43b56a476fc3b28324bc0d7ea3751f463fa Mon Sep 17 00:00:00 2001 From: Nicolas Mouginot Date: Fri, 14 Nov 2025 16:57:56 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove=20the=20generic?= =?UTF-8?q?=20type=20from=20mutation=20and=20action,=20we=20are=20not=20us?= =?UTF-8?q?ing=20the=20model=20in=20theses=20cases.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../laravel_rest_api/laravel_rest_api_actions_factory.dart | 5 +---- .../laravel_rest_api/laravel_rest_api_mutate_factory.dart | 5 +---- test/actions_factory_mock_test.dart | 6 +----- test/mutate_factory_mock_test.dart | 5 +---- 4 files changed, 4 insertions(+), 17 deletions(-) diff --git a/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_actions_factory.dart b/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_actions_factory.dart index fe52a5a..8a40ae0 100644 --- a/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_actions_factory.dart +++ b/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_actions_factory.dart @@ -3,16 +3,13 @@ import 'package:laravel_rest_api_flutter/data/core/models/laravel_rest_api/body/ import '../../rest_api_repository.dart'; /// A mixin to simplify building search requests for Laravel Rest API. -mixin ActionsFactory { +mixin ActionsFactory { /// The base route for the resource (e.g., '/posts'). String get baseRoute; /// The HTTP client used to send requests. Typically a Dio instance. RestApiClient get httpClient; - /// Converts a JSON response into an instance of type `T`. - T fromJson(Map json); - Future> actions({ required LaravelRestApiActionsBody data, Map? headers, diff --git a/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_mutate_factory.dart b/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_mutate_factory.dart index 3b984bc..9313b66 100644 --- a/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_mutate_factory.dart +++ b/lib/data/core/rest_api_factories/laravel_rest_api/laravel_rest_api_mutate_factory.dart @@ -4,16 +4,13 @@ import 'package:laravel_rest_api_flutter/data/core/models/laravel_rest_api/respo import '../../rest_api_repository.dart'; /// A mixin to simplify building search requests for Laravel Rest API. -mixin MutateFactory { +mixin MutateFactory { /// The base route for the resource (e.g., '/posts'). String get baseRoute; /// The HTTP client used to send requests. Typically a http or Dio instance. RestApiClient get httpClient; - /// Converts a JSON response into an instance of type `T`. - T fromJson(Map json); - Future> mutate({ required LaravelRestApiMutateBody body, Map? headers, diff --git a/test/actions_factory_mock_test.dart b/test/actions_factory_mock_test.dart index 3a299b9..836985f 100644 --- a/test/actions_factory_mock_test.dart +++ b/test/actions_factory_mock_test.dart @@ -5,11 +5,10 @@ import 'package:laravel_rest_api_flutter/data/core/rest_api_factories/laravel_re import 'package:mockito/mockito.dart'; import 'package:dio/dio.dart'; -import 'mock/item_model.dart'; import 'mock/mock_http_client.dart'; import 'mock/mock_http_client.mocks.dart'; -class ItemRepository with ActionsFactory { +class ItemRepository with ActionsFactory { MockDio mockDio; ItemRepository(this.mockDio); @@ -18,9 +17,6 @@ class ItemRepository with ActionsFactory { @override RestApiClient get httpClient => MockApiHttpClient(dio: mockDio); - - @override - ItemModel fromJson(Map item) => ItemModel.fromJson(item); } void main() { diff --git a/test/mutate_factory_mock_test.dart b/test/mutate_factory_mock_test.dart index c067866..be4811b 100644 --- a/test/mutate_factory_mock_test.dart +++ b/test/mutate_factory_mock_test.dart @@ -9,7 +9,7 @@ import 'mock/item_model.dart'; import 'mock/mock_http_client.dart'; import 'mock/mock_http_client.mocks.dart'; -class ItemRepository with MutateFactory { +class ItemRepository with MutateFactory { MockDio mockDio; ItemRepository(this.mockDio); @@ -18,9 +18,6 @@ class ItemRepository with MutateFactory { @override RestApiClient get httpClient => MockApiHttpClient(dio: mockDio); - - @override - ItemModel fromJson(Map item) => ItemModel.fromJson(item); } void main() {