Skip to content

Commit 00eeabf

Browse files
Fix: A selectable's selection under the active selection should not be cleared on right-click (flutter#151851)
Fixes flutter#150268 The issue was related to the check for selection geometry here: https://github.com/flutter/flutter/blob/22a5c6cb0a906c219e360ab72da0935aa7a18b93/packages/flutter/lib/src/widgets/selectable_region.dart#L2469-L2476 . Since `otherList == myList` is a reference check this would fail even if the selection rects inside the list contained in SelectionGeometry where the same causing the selectables inside the selection but outside the selectable containing the tapped position to have their selection cleared, use `listEquals` instead.
1 parent e7f39c1 commit 00eeabf

File tree

2 files changed

+77
-1
lines changed

2 files changed

+77
-1
lines changed

packages/flutter/lib/src/rendering/selection.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ class SelectionGeometry {
713713
return other is SelectionGeometry
714714
&& other.startSelectionPoint == startSelectionPoint
715715
&& other.endSelectionPoint == endSelectionPoint
716-
&& other.selectionRects == selectionRects
716+
&& listEquals(other.selectionRects, selectionRects)
717717
&& other.status == status
718718
&& other.hasContent == hasContent;
719719
}

packages/flutter/test/widgets/selectable_region_test.dart

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2358,6 +2358,82 @@ void main() {
23582358
skip: kIsWeb, // [intended] Web uses its native context menu.
23592359
);
23602360

2361+
testWidgets(
2362+
'right-click mouse on an active selection does not clear the selection in other selectables on Apple platforms',
2363+
(WidgetTester tester) async {
2364+
// Regression test for https://github.com/flutter/flutter/issues/150268.
2365+
Set<ContextMenuButtonType> buttonTypes = <ContextMenuButtonType>{};
2366+
final UniqueKey toolbarKey = UniqueKey();
2367+
final FocusNode focusNode = FocusNode();
2368+
addTearDown(focusNode.dispose);
2369+
2370+
await tester.pumpWidget(
2371+
MaterialApp(
2372+
home: SelectableRegion(
2373+
focusNode: focusNode,
2374+
selectionControls: materialTextSelectionHandleControls,
2375+
contextMenuBuilder: (
2376+
BuildContext context,
2377+
SelectableRegionState selectableRegionState,
2378+
) {
2379+
buttonTypes = selectableRegionState.contextMenuButtonItems
2380+
.map((ContextMenuButtonItem buttonItem) => buttonItem.type)
2381+
.toSet();
2382+
return SizedBox.shrink(key: toolbarKey);
2383+
},
2384+
child: const Column(
2385+
children: <Widget>[
2386+
Text('How are you?'),
2387+
Text('Good, and you?'),
2388+
Text('Fine, thank you.'),
2389+
],
2390+
),
2391+
),
2392+
),
2393+
);
2394+
2395+
expect(buttonTypes.isEmpty, true);
2396+
expect(find.byKey(toolbarKey), findsNothing);
2397+
2398+
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
2399+
final RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Good, and you?'), matching: find.byType(RichText)));
2400+
final RenderParagraph paragraph3 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Fine, thank you.'), matching: find.byType(RichText)));
2401+
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse);
2402+
final TestGesture secondaryMouseButtonGesture = await tester.createGesture(kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton);
2403+
addTearDown(secondaryMouseButtonGesture.removePointer);
2404+
addTearDown(gesture.removePointer);
2405+
await tester.pump();
2406+
await gesture.moveTo(textOffsetToPosition(paragraph3, 5));
2407+
await tester.pump();
2408+
await gesture.up();
2409+
await tester.pumpAndSettle();
2410+
expect(paragraph.selections, isNotEmpty);
2411+
expect(paragraph2.selections, isNotEmpty);
2412+
expect(paragraph3.selections, isNotEmpty);
2413+
expect(paragraph.selections[0], const TextSelection(baseOffset: 2, extentOffset: 12));
2414+
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 14));
2415+
expect(paragraph3.selections[0], const TextSelection(baseOffset: 0, extentOffset: 5));
2416+
2417+
// Right-clicking on the active selection should retain the selection.
2418+
await secondaryMouseButtonGesture.down(textOffsetToPosition(paragraph2, 7));
2419+
await tester.pump();
2420+
await secondaryMouseButtonGesture.up();
2421+
await tester.pumpAndSettle();
2422+
expect(paragraph.selections, isNotEmpty);
2423+
expect(paragraph2.selections, isNotEmpty);
2424+
expect(paragraph3.selections, isNotEmpty);
2425+
expect(paragraph.selections[0], const TextSelection(baseOffset: 2, extentOffset: 12));
2426+
expect(paragraph2.selections[0], const TextSelection(baseOffset: 0, extentOffset: 14));
2427+
expect(paragraph3.selections[0], const TextSelection(baseOffset: 0, extentOffset: 5));
2428+
2429+
expect(buttonTypes, contains(ContextMenuButtonType.copy));
2430+
expect(buttonTypes, contains(ContextMenuButtonType.selectAll));
2431+
expect(find.byKey(toolbarKey), findsOneWidget);
2432+
},
2433+
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
2434+
skip: kIsWeb, // [intended] Web uses its native context menu.
2435+
);
2436+
23612437
testWidgets(
23622438
'right-click mouse at the same position as previous right-click toggles the context menu on macOS',
23632439
(WidgetTester tester) async {

0 commit comments

Comments
 (0)