From 83c4db60608aaec939e6469f8583f794ba680749 Mon Sep 17 00:00:00 2001 From: Nicolas Mouginot Date: Tue, 28 Oct 2025 17:26:10 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20TextSearch()=20to=20SearchFac?= =?UTF-8?q?tory()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../laravel_rest_api_search_factory.dart | 14 +- test/search_factory_mock_test.dart | 158 +++++++++--------- 2 files changed, 86 insertions(+), 86 deletions(-) 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..fbbd2b6 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 @@ -28,6 +28,10 @@ mixin SearchFactory { /// Performs a search request and returns a list of items of type `T`. Future>> search({ + /// **Text Search**: Allows for full-text search on enabled resources. + /// Use the `text` parameter with a `value` containing your search terms. + TextSearch? text, + /// **Scopes**: Allows for scoped queries defined in your Laravel resource. /// Specify the scope name and its parameters. List? scopes, @@ -70,6 +74,7 @@ mixin SearchFactory { final requestBody = { "search": { ...?defaultSearchBody?.toJson(), + if (text != null) 'text': text.toJson(), if (scopes != null) 'scopes': scopes.map((e) => e.toJson()).toList(), if (filters != null) 'filters': filters.map((e) => e.toJson()).toList(), @@ -84,7 +89,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 +122,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/test/search_factory_mock_test.dart b/test/search_factory_mock_test.dart index afce78e..acd7c14 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,22 @@ 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"), - ], + text: TextSearch(value: "my text search"), + 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,12 +184,13 @@ 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('text'), isTrue); expect(capturedArgs[0]["search"].containsKey('filters'), isTrue); expect(capturedArgs[0]["search"].containsKey('aggregates'), isTrue); expect(capturedArgs[0]["search"].containsKey('includes'), isTrue); @@ -206,23 +202,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);