-
Notifications
You must be signed in to change notification settings - Fork 23
Adjust start offset calculation for query selection #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| var start = querySelection.baseOffset; | ||
| if (!keepTrigger) start -= 1; | ||
| if (!keepTrigger) start -= _currentTrigger.trigger.length; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
LGTM! Correctly fixes multi-character trigger handling.
The change properly accounts for triggers longer than one character. The previous hardcoded start -= 1 only worked for single-character triggers. Using _currentTrigger.trigger.length ensures the entire trigger is included in the replacement range when keepTrigger is false.
This affects both:
- The text replacement at line 242:
text.replaceRange(start, end, option) - The cursor position calculation at line 237:
selectionOffset = start + option.length
Consider verifying that tests cover multi-character triggers (e.g., "@@", "###") to prevent regression. You can check if test files exist and validate coverage:
🏁 Script executed:
#!/bin/bash
# Find test files and search for multi-character trigger test cases
fd -e dart -t f test | xargs rg -l "acceptAutocompleteOption|keepTrigger" | head -5
# Check for test cases with multi-character triggers
rg -A 10 "keepTrigger.*false|multi.*character.*trigger" --type dart -g '*test*.dart'Length of output: 41
🏁 Script executed:
#!/bin/bash
# Read the test file to examine test coverage
cat test/multi_trigger_autocomplete_test.dart | head -200
# Search for test groups or test functions related to acceptAutocompleteOption
rg -n "test|group|acceptAutocompleteOption" test/multi_trigger_autocomplete_test.dart | head -30Length of output: 8267
🏁 Script executed:
#!/bin/bash
# Search for keepTrigger in test file
echo "=== Searching for keepTrigger usage ==="
rg -n "keepTrigger" test/multi_trigger_autocomplete_test.dart
# Get file length and look for multi-character trigger patterns
echo "=== File statistics and multi-character trigger patterns ==="
wc -l test/multi_trigger_autocomplete_test.dart
rg -n "trigger.*[:=].*['\"][@#]{2,}" test/multi_trigger_autocomplete_test.dart
# List all test functions/groups
echo "=== All test functions in file ==="
rg -n "testWidgets|test\(" test/multi_trigger_autocomplete_test.dart | wc -l
rg -n "testWidgets\|^.*test\(" test/multi_trigger_autocomplete_test.dart | head -20Length of output: 184
Add tests for multi-character triggers with keepTrigger=false.
The fix correctly updates the trigger offset calculation, but verification shows zero test coverage for this scenario. The test file contains 10 test functions, none of which use the keepTrigger parameter or multi-character triggers.
Add tests covering:
- Multi-character triggers (e.g.,
"@@","###") withkeepTrigger=false - Correct text replacement range when entire multi-character trigger is removed
- Cursor position after autocomplete selection
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
lib/src/multi_trigger_autocomplete.dart (1)
229-229: Bug fix correctly handles multi-character triggers.The change properly accounts for triggers longer than one character by using the actual trigger length instead of the hardcoded
1. This fixes the issue wherekeepTrigger=falsewould only remove one character of multi-character triggers like"@@"or"###".Optional: Use the local
triggervariable for consistency.Since
triggeris already null-checked at line 223, you can simplify line 229 to use the local variable directly:- if (!keepTrigger) start -= _currentTrigger?.trigger.length ?? 1; + if (!keepTrigger) start -= trigger.trigger.length;This eliminates the unnecessary null-safety operator and fallback, making the code more consistent with the pattern established by the null check above.
|
Hey @ViscousPot , can you add some tests for this fix? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| testWidgets('handles multi-character triggers with keepTrigger=false', (tester) async { | ||
| final multiCharTrigger = AutocompleteTrigger( | ||
| trigger: '##' | ||
| optionsViewBuilder: (context, query, controller) { }, | ||
| ); | ||
|
|
||
| await tester.pumpWidget( | ||
| Boilerplate( | ||
| child: MultiTriggerAutocomplete( | ||
| autocompleteTriggers: [multiCharTrigger], | ||
| ), | ||
| ), | ||
| ); | ||
|
|
||
| await tester.enterText(find.byType(TextFormField), '##'); | ||
|
|
||
| await tester.tap(find.byType(InkWell).first); | ||
| await tester.pump(); | ||
|
|
||
| final TextFormField field = | ||
| find.byType(TextFormField).evaluate().first.widget as TextFormField; | ||
|
|
||
| expect(field.controller!.text, isNot(contains('##'))); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Test has syntax errors and will fail at runtime.
This test has multiple issues that will cause it to fail:
- Syntax error on line 303: Missing comma after
trigger: '##' - Empty optionsViewBuilder on line 304: The function body
{ }doesn't return a Widget as required. It must return a widget like ListView with tappable items. - Line 317 will fail: Attempting to tap
find.byType(InkWell).firstbut the empty optionsViewBuilder doesn't create any InkWell widgets, causing the finder to fail. - Missing debounce wait: After line 315's
enterText, you should addawait tester.pumpAndSettle(kDebounceDuration);to allow the options to appear (see line 159 for reference). - No verification of options: Should verify
find.byType(ListView)finds a widget before attempting to tap.
Apply this diff to fix the test:
- testWidgets('handles multi-character triggers with keepTrigger=false', (tester) async {
- final multiCharTrigger = AutocompleteTrigger(
- trigger: '##'
- optionsViewBuilder: (context, query, controller) { },
- );
-
- await tester.pumpWidget(
- Boilerplate(
- child: MultiTriggerAutocomplete(
- autocompleteTriggers: [multiCharTrigger],
- ),
- ),
- );
-
- await tester.enterText(find.byType(TextFormField), '##');
-
- await tester.tap(find.byType(InkWell).first);
- await tester.pump();
-
- final TextFormField field =
- find.byType(TextFormField).evaluate().first.widget as TextFormField;
-
- expect(field.controller!.text, isNot(contains('##')));
- });
+ testWidgets('handles multi-character triggers with keepTrigger=false', (tester) async {
+ final multiCharTrigger = AutocompleteTrigger(
+ trigger: '##',
+ optionsViewBuilder: (context, query, controller) {
+ return ListView.builder(
+ itemCount: 1,
+ itemBuilder: (context, index) {
+ return ListTile(
+ title: const Text('Option'),
+ onTap: () {
+ final autocomplete = MultiTriggerAutocomplete.of(context);
+ return autocomplete.acceptAutocompleteOption('Option');
+ },
+ );
+ },
+ );
+ },
+ );
+
+ await tester.pumpWidget(
+ Boilerplate(
+ child: MultiTriggerAutocomplete(
+ debounceDuration: kDebounceDuration,
+ autocompleteTriggers: [multiCharTrigger],
+ ),
+ ),
+ );
+
+ await tester.enterText(find.byType(TextFormField), '##');
+ await tester.pumpAndSettle(kDebounceDuration);
+ expect(find.byType(ListView), findsOneWidget);
+
+ await tester.tap(find.byType(InkWell).first);
+ await tester.pump();
+
+ final TextFormField field =
+ find.byType(TextFormField).evaluate().first.widget as TextFormField;
+
+ expect(field.controller!.text, isNot(contains('##')));
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| testWidgets('handles multi-character triggers with keepTrigger=false', (tester) async { | |
| final multiCharTrigger = AutocompleteTrigger( | |
| trigger: '##' | |
| optionsViewBuilder: (context, query, controller) { }, | |
| ); | |
| await tester.pumpWidget( | |
| Boilerplate( | |
| child: MultiTriggerAutocomplete( | |
| autocompleteTriggers: [multiCharTrigger], | |
| ), | |
| ), | |
| ); | |
| await tester.enterText(find.byType(TextFormField), '##'); | |
| await tester.tap(find.byType(InkWell).first); | |
| await tester.pump(); | |
| final TextFormField field = | |
| find.byType(TextFormField).evaluate().first.widget as TextFormField; | |
| expect(field.controller!.text, isNot(contains('##'))); | |
| }); | |
| testWidgets('handles multi-character triggers with keepTrigger=false', (tester) async { | |
| final multiCharTrigger = AutocompleteTrigger( | |
| trigger: '##', | |
| optionsViewBuilder: (context, query, controller) { | |
| return ListView.builder( | |
| itemCount: 1, | |
| itemBuilder: (context, index) { | |
| return ListTile( | |
| title: const Text('Option'), | |
| onTap: () { | |
| final autocomplete = MultiTriggerAutocomplete.of(context); | |
| return autocomplete.acceptAutocompleteOption('Option'); | |
| }, | |
| ); | |
| }, | |
| ); | |
| }, | |
| ); | |
| await tester.pumpWidget( | |
| Boilerplate( | |
| child: MultiTriggerAutocomplete( | |
| debounceDuration: kDebounceDuration, | |
| autocompleteTriggers: [multiCharTrigger], | |
| ), | |
| ), | |
| ); | |
| await tester.enterText(find.byType(TextFormField), '##'); | |
| await tester.pumpAndSettle(kDebounceDuration); | |
| expect(find.byType(ListView), findsOneWidget); | |
| await tester.tap(find.byType(InkWell).first); | |
| await tester.pump(); | |
| final TextFormField field = | |
| find.byType(TextFormField).evaluate().first.widget as TextFormField; | |
| expect(field.controller!.text, isNot(contains('##'))); | |
| }); |
| testWidgets('cursor position correct after autocomplete with multi-character trigger', (tester) async { | ||
| final multiCharTrigger = AutocompleteTrigger( | ||
| trigger: '@@', | ||
| optionsViewBuilder: (context, query, controller) { }, | ||
| ); | ||
|
|
||
| await tester.pumpWidget( | ||
| Boilerplate( | ||
| child: MultiTriggerAutocomplete( | ||
| autocompleteTriggers: [multiCharTrigger], | ||
| ), | ||
| ), | ||
| ); | ||
|
|
||
| await tester.enterText(find.byType(TextFormField), '@@sa'); | ||
|
|
||
| await tester.tap(find.byType(InkWell).first); | ||
| await tester.pump(); | ||
|
|
||
| final TextFormField field = | ||
| find.byType(TextFormField).evaluate().first.widget as TextFormField; | ||
|
|
||
| expect(field.controller!.selection.baseOffset, | ||
| equals(field.controller!.text.length)); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Test has syntax errors and will fail at runtime.
This test has the same issues as the previous test:
- Syntax error on line 328: Missing comma after
trigger: '@@' - Empty optionsViewBuilder on line 329: Must return a Widget like ListView with tappable items.
- Line 342 will fail: Attempting to tap a non-existent InkWell.
- Missing debounce wait: After line 340's
enterText, addawait tester.pumpAndSettle(kDebounceDuration);. - No verification of options: Should verify ListView is shown before tapping.
Apply this diff to fix the test:
- testWidgets('cursor position correct after autocomplete with multi-character trigger', (tester) async {
- final multiCharTrigger = AutocompleteTrigger(
- trigger: '@@',
- optionsViewBuilder: (context, query, controller) { },
- );
-
- await tester.pumpWidget(
- Boilerplate(
- child: MultiTriggerAutocomplete(
- autocompleteTriggers: [multiCharTrigger],
- ),
- ),
- );
-
- await tester.enterText(find.byType(TextFormField), '@@sa');
-
- await tester.tap(find.byType(InkWell).first);
- await tester.pump();
-
- final TextFormField field =
- find.byType(TextFormField).evaluate().first.widget as TextFormField;
-
- expect(field.controller!.selection.baseOffset,
- equals(field.controller!.text.length));
- });
+ testWidgets('cursor position correct after autocomplete with multi-character trigger', (tester) async {
+ final multiCharTrigger = AutocompleteTrigger(
+ trigger: '@@',
+ optionsViewBuilder: (context, query, controller) {
+ return ListView.builder(
+ itemCount: 1,
+ itemBuilder: (context, index) {
+ return ListTile(
+ title: const Text('Option'),
+ onTap: () {
+ final autocomplete = MultiTriggerAutocomplete.of(context);
+ return autocomplete.acceptAutocompleteOption('Option');
+ },
+ );
+ },
+ );
+ },
+ );
+
+ await tester.pumpWidget(
+ Boilerplate(
+ child: MultiTriggerAutocomplete(
+ debounceDuration: kDebounceDuration,
+ autocompleteTriggers: [multiCharTrigger],
+ ),
+ ),
+ );
+
+ await tester.enterText(find.byType(TextFormField), '@@sa');
+ await tester.pumpAndSettle(kDebounceDuration);
+ expect(find.byType(ListView), findsOneWidget);
+
+ await tester.tap(find.byType(InkWell).first);
+ await tester.pump();
+
+ final TextFormField field =
+ find.byType(TextFormField).evaluate().first.widget as TextFormField;
+
+ expect(field.controller!.selection.baseOffset,
+ equals(field.controller!.text.length));
+ });Refactor autocomplete test to include options view builder.
Description
If trigger is longer than one character,
keepTriggerdoesn't work as expected. This fixes that :)Type of Change
Summary by CodeRabbit
Bug Fixes
Tests