From 8707377ec486f95e21605a7b35a211752b0820c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Ma=C3=B1osa?= Date: Sun, 4 Feb 2024 23:54:51 +0100 Subject: [PATCH] =?UTF-8?q?refactor(search=5Fcondition):=20=E2=99=BB?= =?UTF-8?q?=EF=B8=8F=20move=20`highlightedRange`=20as=20method=20(#51)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(search_condition): ♻️ move `highlightedRange` as method * docs(search_condition): 📖 add more examples for `highlightedRange` * test(main): 🧪 add `search_condition_test` --- lib/src/model/search_condition.dart | 38 ++++++++++++ lib/src/widgets/autocomplete_entry_card.dart | 18 +----- test/main.dart | 2 + test/model/search_condition_test.dart | 61 ++++++++++++++++++++ 4 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 test/model/search_condition_test.dart diff --git a/lib/src/model/search_condition.dart b/lib/src/model/search_condition.dart index e2bff85..f0b85f8 100644 --- a/lib/src/model/search_condition.dart +++ b/lib/src/model/search_condition.dart @@ -1,3 +1,5 @@ +import 'package:diacritic/diacritic.dart'; + enum SearchCondition { coincident, startingWith, @@ -9,6 +11,42 @@ enum SearchCondition { static const defaultCondition = startingWith; + /// Returns the highlighted range for [query] in [word] based on this + /// [SearchCondition]. + /// + /// Example: + /// ```dart + /// SearchCondition.startingWith.highlightedRange('pla', 'pl') == (0, 2) + /// SearchCondition.endingIn.highlightedRange('alçària', 'aria') == (3, 7) + /// SearchCondition.doesNotContain.highlightedRange('alçària', 'b') == null + /// ``` + (int start, int end)? highlightedRange(String word, String query) { + switch (this) { + case coincident: + return (0, word.length); + + case endingIn: + var lastIndex = word.lastIndexOf(query); + if (lastIndex == -1) { + lastIndex = + removeDiacritics(word).lastIndexOf(removeDiacritics(query)); + } + if (lastIndex == -1) return null; + return (lastIndex, lastIndex + query.length); + + case startingWith || inAnyPosition: + var index = word.indexOf(query); + if (index == -1) { + index = removeDiacritics(word).indexOf(removeDiacritics(query)); + } + if (index == -1) return null; + return (index, index + query.length); + + default: + return null; + } + } + String translate() => switch (this) { coincident => 'Coincident', startingWith => 'Començada per', diff --git a/lib/src/widgets/autocomplete_entry_card.dart b/lib/src/widgets/autocomplete_entry_card.dart index d6ed73b..b60e418 100644 --- a/lib/src/widgets/autocomplete_entry_card.dart +++ b/lib/src/widgets/autocomplete_entry_card.dart @@ -1,4 +1,3 @@ -import 'package:diacritic/diacritic.dart'; import 'package:el_meu_diec/model.dart'; import 'package:el_meu_diec/src/constants.dart'; import 'package:el_meu_diec/src/pages/word_page.dart'; @@ -25,26 +24,13 @@ class AutocompleteEntryCard extends StatelessWidget { bool get isIncomplete => word.word.length > query.length; - (int start, int end) get highlightedRange => switch (searchCondition) { - SearchCondition.startingWith => (0, query.length), - SearchCondition.endingIn => ( - word.word.length - query.length, - word.word.length, - ), - SearchCondition.inAnyPosition => ( - removeDiacritics(word.word).indexOf(removeDiacritics(query)), - removeDiacritics(word.word).indexOf(removeDiacritics(query)) + - query.length, - ), - _ => (0, word.word.length), - }; - @override Widget build(BuildContext context) { final theme = Theme.of(context); final headlineTextStyle = theme.textTheme.headlineTextStyle; - final (start, end) = highlightedRange; + final (start, end) = searchCondition.highlightedRange(word.word, query) ?? + (0, word.word.length); final isIncompleteStart = start > 0; final isIncompleteEnd = end < word.word.length; diff --git a/test/main.dart b/test/main.dart index a02b610..dfeaca1 100644 --- a/test/main.dart +++ b/test/main.dart @@ -1,9 +1,11 @@ import 'model/definition_entry_sense_test.dart' as definition_entry_sense_test; import 'model/gender_test.dart' as gender_test; import 'model/scope_test.dart' as scope_test; +import 'model/search_condition_test.dart' as search_condition_test; void main() { definition_entry_sense_test.main(); gender_test.main(); scope_test.main(); + search_condition_test.main(); } diff --git a/test/model/search_condition_test.dart b/test/model/search_condition_test.dart new file mode 100644 index 0000000..74c1367 --- /dev/null +++ b/test/model/search_condition_test.dart @@ -0,0 +1,61 @@ +import 'package:el_meu_diec/model.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('SearchCondition', () { + group('.highlightedRange()', () { + test('should return the highlighted range in word from query', () { + expect( + SearchCondition.coincident.highlightedRange('dart', 'a'), + const (0, 4), + ); + + expect( + SearchCondition.startingWith.highlightedRange('dart', 'd'), + const (0, 1), + ); + expect( + SearchCondition.startingWith.highlightedRange('alçària', 'alça'), + const (0, 4), + ); + + expect( + SearchCondition.endingIn.highlightedRange('dart', 'rt'), + const (2, 4), + ); + expect( + SearchCondition.endingIn.highlightedRange('dart2', 't'), + const (3, 4), + ); + expect( + SearchCondition.endingIn.highlightedRange('fabària', 'aria'), + const (3, 7), + ); + + expect( + SearchCondition.inAnyPosition.highlightedRange('dart', 'a'), + const (1, 2), + ); + }); + + test('should return null when no range to highlight is found', () { + expect( + SearchCondition.startingWith.highlightedRange('dart', 'c'), + isNull, + ); + expect( + SearchCondition.notStartingWith.highlightedRange('dart', 'f'), + isNull, + ); + expect( + SearchCondition.notEndingIn.highlightedRange('dart', 'f'), + isNull, + ); + expect( + SearchCondition.doesNotContain.highlightedRange('dart', 'f'), + isNull, + ); + }); + }); + }); +}