Add selection feedback for both selection area and text field (#115373)
* Add selection feedback for both selection area and text field * Addressing comment * Fixes more test
This commit is contained in:
parent
a1ea383fa5
commit
c2b29501f4
@ -52,6 +52,19 @@ class TextSelectionPoint {
|
||||
/// Direction of the text at this edge of the selection.
|
||||
final TextDirection? direction;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
if (other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
return other is TextSelectionPoint
|
||||
&& other.point == point
|
||||
&& other.direction == direction;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
switch (direction) {
|
||||
@ -63,6 +76,10 @@ class TextSelectionPoint {
|
||||
return '$point';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(point, direction);
|
||||
|
||||
}
|
||||
|
||||
/// The consecutive sequence of [TextPosition]s that the caret should move to
|
||||
|
@ -467,6 +467,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
}
|
||||
|
||||
void _handleTouchLongPressStart(LongPressStartDetails details) {
|
||||
HapticFeedback.selectionClick();
|
||||
widget.focusNode.requestFocus();
|
||||
_selectWordAt(offset: details.globalPosition);
|
||||
_showToolbar();
|
||||
@ -537,6 +538,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe
|
||||
},
|
||||
);
|
||||
}
|
||||
_stopSelectionStartEdgeUpdate();
|
||||
_stopSelectionEndEdgeUpdate();
|
||||
_updateSelectedContentIfNeeded();
|
||||
}
|
||||
|
@ -515,6 +515,11 @@ class TextSelectionOverlay {
|
||||
}
|
||||
_value = newValue;
|
||||
_updateSelectionOverlay();
|
||||
// _updateSelectionOverlay may not rebuild the selection overlay if the
|
||||
// text metrics and selection doesn't change even if the text has changed.
|
||||
// This rebuild is needed for the toolbar to update based on the latest text
|
||||
// value.
|
||||
_selectionOverlay.markNeedsBuild();
|
||||
}
|
||||
|
||||
void _updateSelectionOverlay() {
|
||||
@ -541,7 +546,13 @@ class TextSelectionOverlay {
|
||||
///
|
||||
/// This is intended to be called when the [renderObject] may have changed its
|
||||
/// text metrics (e.g. because the text was scrolled).
|
||||
void updateForScroll() => _updateSelectionOverlay();
|
||||
void updateForScroll() {
|
||||
_updateSelectionOverlay();
|
||||
// This method may be called due to windows metrics changes. In that case,
|
||||
// non of the properties in _selectionOverlay will change, but a rebuild is
|
||||
// still needed.
|
||||
_selectionOverlay.markNeedsBuild();
|
||||
}
|
||||
|
||||
/// Whether the handles are currently visible.
|
||||
bool get handlesAreVisible => _selectionOverlay._handles != null && handlesVisible;
|
||||
@ -1030,7 +1041,7 @@ class SelectionOverlay {
|
||||
return;
|
||||
}
|
||||
_startHandleType = value;
|
||||
_markNeedsBuild();
|
||||
markNeedsBuild();
|
||||
}
|
||||
|
||||
/// The line height at the selection start.
|
||||
@ -1045,9 +1056,11 @@ class SelectionOverlay {
|
||||
return;
|
||||
}
|
||||
_lineHeightAtStart = value;
|
||||
_markNeedsBuild();
|
||||
markNeedsBuild();
|
||||
}
|
||||
|
||||
bool _isDraggingStartHandle = false;
|
||||
|
||||
/// Whether the start handle is visible.
|
||||
///
|
||||
/// If the value changes, the start handle uses [FadeTransition] to transition
|
||||
@ -1059,6 +1072,12 @@ class SelectionOverlay {
|
||||
/// Called when the users start dragging the start selection handles.
|
||||
final ValueChanged<DragStartDetails>? onStartHandleDragStart;
|
||||
|
||||
void _handleStartHandleDragStart(DragStartDetails details) {
|
||||
assert(!_isDraggingStartHandle);
|
||||
_isDraggingStartHandle = details.kind == PointerDeviceKind.touch;
|
||||
onStartHandleDragStart?.call(details);
|
||||
}
|
||||
|
||||
/// Called when the users drag the start selection handles to new locations.
|
||||
final ValueChanged<DragUpdateDetails>? onStartHandleDragUpdate;
|
||||
|
||||
@ -1066,6 +1085,11 @@ class SelectionOverlay {
|
||||
/// handles.
|
||||
final ValueChanged<DragEndDetails>? onStartHandleDragEnd;
|
||||
|
||||
void _handleStartHandleDragEnd(DragEndDetails details) {
|
||||
_isDraggingStartHandle = false;
|
||||
onStartHandleDragEnd?.call(details);
|
||||
}
|
||||
|
||||
/// The type of end selection handle.
|
||||
///
|
||||
/// Changing the value while the handles are visible causes them to rebuild.
|
||||
@ -1076,7 +1100,7 @@ class SelectionOverlay {
|
||||
return;
|
||||
}
|
||||
_endHandleType = value;
|
||||
_markNeedsBuild();
|
||||
markNeedsBuild();
|
||||
}
|
||||
|
||||
/// The line height at the selection end.
|
||||
@ -1091,9 +1115,11 @@ class SelectionOverlay {
|
||||
return;
|
||||
}
|
||||
_lineHeightAtEnd = value;
|
||||
_markNeedsBuild();
|
||||
markNeedsBuild();
|
||||
}
|
||||
|
||||
bool _isDraggingEndHandle = false;
|
||||
|
||||
/// Whether the end handle is visible.
|
||||
///
|
||||
/// If the value changes, the end handle uses [FadeTransition] to transition
|
||||
@ -1105,6 +1131,12 @@ class SelectionOverlay {
|
||||
/// Called when the users start dragging the end selection handles.
|
||||
final ValueChanged<DragStartDetails>? onEndHandleDragStart;
|
||||
|
||||
void _handleEndHandleDragStart(DragStartDetails details) {
|
||||
assert(!_isDraggingEndHandle);
|
||||
_isDraggingEndHandle = details.kind == PointerDeviceKind.touch;
|
||||
onEndHandleDragStart?.call(details);
|
||||
}
|
||||
|
||||
/// Called when the users drag the end selection handles to new locations.
|
||||
final ValueChanged<DragUpdateDetails>? onEndHandleDragUpdate;
|
||||
|
||||
@ -1112,6 +1144,11 @@ class SelectionOverlay {
|
||||
/// handles.
|
||||
final ValueChanged<DragEndDetails>? onEndHandleDragEnd;
|
||||
|
||||
void _handleEndHandleDragEnd(DragEndDetails details) {
|
||||
_isDraggingEndHandle = false;
|
||||
onEndHandleDragEnd?.call(details);
|
||||
}
|
||||
|
||||
/// Whether the toolbar is visible.
|
||||
///
|
||||
/// If the value changes, the toolbar uses [FadeTransition] to transition
|
||||
@ -1125,7 +1162,21 @@ class SelectionOverlay {
|
||||
List<TextSelectionPoint> _selectionEndpoints;
|
||||
set selectionEndpoints(List<TextSelectionPoint> value) {
|
||||
if (!listEquals(_selectionEndpoints, value)) {
|
||||
_markNeedsBuild();
|
||||
markNeedsBuild();
|
||||
if ((_isDraggingEndHandle || _isDraggingStartHandle) &&
|
||||
_startHandleType != TextSelectionHandleType.collapsed) {
|
||||
switch(defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
HapticFeedback.selectionClick();
|
||||
break;
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.macOS:
|
||||
case TargetPlatform.windows:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_selectionEndpoints = value;
|
||||
}
|
||||
@ -1220,7 +1271,7 @@ class SelectionOverlay {
|
||||
return;
|
||||
}
|
||||
_toolbarLocation = value;
|
||||
_markNeedsBuild();
|
||||
markNeedsBuild();
|
||||
}
|
||||
|
||||
/// Controls the fade-in and fade-out animations for the toolbar and handles.
|
||||
@ -1250,7 +1301,6 @@ class SelectionOverlay {
|
||||
OverlayEntry(builder: _buildStartHandle),
|
||||
OverlayEntry(builder: _buildEndHandle),
|
||||
];
|
||||
|
||||
Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor).insertAll(_handles!);
|
||||
}
|
||||
|
||||
@ -1299,7 +1349,9 @@ class SelectionOverlay {
|
||||
}
|
||||
|
||||
bool _buildScheduled = false;
|
||||
void _markNeedsBuild() {
|
||||
|
||||
/// Rebuilds the selection toolbar or handles if they are present.
|
||||
void markNeedsBuild() {
|
||||
if (_handles == null && _toolbar == null) {
|
||||
return;
|
||||
}
|
||||
@ -1379,9 +1431,9 @@ class SelectionOverlay {
|
||||
type: _startHandleType,
|
||||
handleLayerLink: startHandleLayerLink,
|
||||
onSelectionHandleTapped: onSelectionHandleTapped,
|
||||
onSelectionHandleDragStart: onStartHandleDragStart,
|
||||
onSelectionHandleDragStart: _handleStartHandleDragStart,
|
||||
onSelectionHandleDragUpdate: onStartHandleDragUpdate,
|
||||
onSelectionHandleDragEnd: onStartHandleDragEnd,
|
||||
onSelectionHandleDragEnd: _handleStartHandleDragEnd,
|
||||
selectionControls: selectionControls,
|
||||
visibility: startHandlesVisible,
|
||||
preferredLineHeight: _lineHeightAtStart,
|
||||
@ -1406,9 +1458,9 @@ class SelectionOverlay {
|
||||
type: _endHandleType,
|
||||
handleLayerLink: endHandleLayerLink,
|
||||
onSelectionHandleTapped: onSelectionHandleTapped,
|
||||
onSelectionHandleDragStart: onEndHandleDragStart,
|
||||
onSelectionHandleDragStart: _handleEndHandleDragStart,
|
||||
onSelectionHandleDragUpdate: onEndHandleDragUpdate,
|
||||
onSelectionHandleDragEnd: onEndHandleDragEnd,
|
||||
onSelectionHandleDragEnd: _handleEndHandleDragEnd,
|
||||
selectionControls: selectionControls,
|
||||
visibility: endHandlesVisible,
|
||||
preferredLineHeight: _lineHeightAtEnd,
|
||||
|
@ -2477,6 +2477,61 @@ void main() {
|
||||
variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
|
||||
);
|
||||
|
||||
testWidgets('Drag handles trigger feedback', (WidgetTester tester) async {
|
||||
final FeedbackTester feedback = FeedbackTester();
|
||||
addTearDown(feedback.dispose);
|
||||
final TextEditingController controller = TextEditingController();
|
||||
await tester.pumpWidget(
|
||||
overlay(
|
||||
child: TextField(
|
||||
dragStartBehavior: DragStartBehavior.down,
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const String testValue = 'abc def ghi';
|
||||
await tester.enterText(find.byType(TextField), testValue);
|
||||
expect(feedback.hapticCount, 0);
|
||||
await skipPastScrollingAnimation(tester);
|
||||
|
||||
// Long press the 'e' to select 'def'.
|
||||
final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e'));
|
||||
TestGesture gesture = await tester.startGesture(ePos, pointer: 7);
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 200)); // skip past the frame where the opacity is zero
|
||||
|
||||
final TextSelection selection = controller.selection;
|
||||
expect(selection.baseOffset, 4);
|
||||
expect(selection.extentOffset, 7);
|
||||
expect(feedback.hapticCount, 1);
|
||||
|
||||
final RenderEditable renderEditable = findRenderEditable(tester);
|
||||
final List<TextSelectionPoint> endpoints = globalize(
|
||||
renderEditable.getEndpointsForSelection(selection),
|
||||
renderEditable,
|
||||
);
|
||||
expect(endpoints.length, 2);
|
||||
|
||||
// Drag the right handle 2 letters to the right.
|
||||
// Use a small offset because the endpoint is on the very corner
|
||||
// of the handle.
|
||||
final Offset handlePos = endpoints[1].point + const Offset(1.0, 1.0);
|
||||
final Offset newHandlePos = textOffsetToPosition(tester, testValue.length);
|
||||
gesture = await tester.startGesture(handlePos, pointer: 7);
|
||||
await tester.pump();
|
||||
await gesture.moveTo(newHandlePos);
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
|
||||
expect(controller.selection.baseOffset, 4);
|
||||
expect(controller.selection.extentOffset, 11);
|
||||
expect(feedback.hapticCount, 2);
|
||||
});
|
||||
|
||||
testWidgets('Cannot drag one handle past the other', (WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController();
|
||||
|
||||
@ -4965,6 +5020,7 @@ void main() {
|
||||
|
||||
testWidgets('haptic feedback', (WidgetTester tester) async {
|
||||
final FeedbackTester feedback = FeedbackTester();
|
||||
addTearDown(feedback.dispose);
|
||||
final TextEditingController controller = TextEditingController();
|
||||
|
||||
await tester.pumpWidget(
|
||||
@ -4987,8 +5043,6 @@ void main() {
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
expect(feedback.clickSoundCount, 0);
|
||||
expect(feedback.hapticCount, 1);
|
||||
|
||||
feedback.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Text field drops selection color when losing focus', (WidgetTester tester) async {
|
||||
|
@ -915,6 +915,20 @@ void main() {
|
||||
expect(endpoints[0].point.dx, 0);
|
||||
});
|
||||
|
||||
test('TextSelectionPoint can compare', () {
|
||||
// ignore: prefer_const_constructors
|
||||
final TextSelectionPoint first = TextSelectionPoint(Offset(1, 2), TextDirection.ltr);
|
||||
// ignore: prefer_const_constructors
|
||||
final TextSelectionPoint second = TextSelectionPoint(Offset(1, 2), TextDirection.ltr);
|
||||
expect(first == second, isTrue);
|
||||
expect(first.hashCode == second.hashCode, isTrue);
|
||||
|
||||
// ignore: prefer_const_constructors
|
||||
final TextSelectionPoint different = TextSelectionPoint(Offset(2, 2), TextDirection.ltr);
|
||||
expect(first == different, isFalse);
|
||||
expect(first.hashCode == different.hashCode, isFalse);
|
||||
});
|
||||
|
||||
group('getRectForComposingRange', () {
|
||||
const TextSpan emptyTextSpan = TextSpan(text: '\u200e');
|
||||
final TextSelectionDelegate delegate = _FakeEditableTextState();
|
||||
|
@ -463,6 +463,7 @@ void main() {
|
||||
|
||||
final List<TextBox> boxes = paragraph0.getBoxesForSelection(paragraph0.selections[0]);
|
||||
expect(boxes.length, 1);
|
||||
// Find end handle.
|
||||
final Offset handlePos = globalize(boxes[0].toRect().bottomRight, paragraph0);
|
||||
await gesture.down(handlePos);
|
||||
|
||||
@ -493,6 +494,113 @@ void main() {
|
||||
await gesture.up();
|
||||
});
|
||||
|
||||
testWidgets('select to scroll by dragging start selection handle stops scroll when released', (WidgetTester tester) async {
|
||||
final ScrollController controller = ScrollController();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: SelectionArea(
|
||||
selectionControls: materialTextSelectionControls,
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
itemCount: 100,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return Text('Item $index');
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Long press to bring up the selection handles.
|
||||
final RenderParagraph paragraph0 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Item 0'), matching: find.byType(RichText)));
|
||||
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph0, 2));
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
await gesture.up();
|
||||
expect(paragraph0.selections[0], const TextSelection(baseOffset: 0, extentOffset: 4));
|
||||
|
||||
final List<TextBox> boxes = paragraph0.getBoxesForSelection(paragraph0.selections[0]);
|
||||
expect(boxes.length, 1);
|
||||
// Find start handle.
|
||||
final Offset handlePos = globalize(boxes[0].toRect().bottomLeft, paragraph0);
|
||||
await gesture.down(handlePos);
|
||||
|
||||
expect(controller.offset, 0.0);
|
||||
double previousOffset = controller.offset;
|
||||
// Scrollable only auto scroll if the drag passes the boundary.
|
||||
await gesture.moveTo(tester.getBottomRight(find.byType(ListView)) + const Offset(0, 40));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
expect(controller.offset > previousOffset, isTrue);
|
||||
previousOffset = controller.offset;
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
expect(controller.offset > previousOffset, isTrue);
|
||||
previousOffset = controller.offset;
|
||||
|
||||
// Release handle should stop scrolling.
|
||||
await gesture.up();
|
||||
// Last scheduled scroll.
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
previousOffset = controller.offset;
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.offset, previousOffset);
|
||||
});
|
||||
|
||||
testWidgets('select to scroll by dragging end selection handle stops scroll when released', (WidgetTester tester) async {
|
||||
final ScrollController controller = ScrollController();
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: SelectionArea(
|
||||
selectionControls: materialTextSelectionControls,
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
itemCount: 100,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return Text('Item $index');
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Long press to bring up the selection handles.
|
||||
final RenderParagraph paragraph0 = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('Item 0'), matching: find.byType(RichText)));
|
||||
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph0, 2));
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
await gesture.up();
|
||||
expect(paragraph0.selections[0], const TextSelection(baseOffset: 0, extentOffset: 4));
|
||||
|
||||
final List<TextBox> boxes = paragraph0.getBoxesForSelection(paragraph0.selections[0]);
|
||||
expect(boxes.length, 1);
|
||||
final Offset handlePos = globalize(boxes[0].toRect().bottomRight, paragraph0);
|
||||
await gesture.down(handlePos);
|
||||
|
||||
expect(controller.offset, 0.0);
|
||||
double previousOffset = controller.offset;
|
||||
// Scrollable only auto scroll if the drag passes the boundary
|
||||
await gesture.moveTo(tester.getBottomRight(find.byType(ListView)) + const Offset(0, 40));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
expect(controller.offset > previousOffset, isTrue);
|
||||
previousOffset = controller.offset;
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
expect(controller.offset > previousOffset, isTrue);
|
||||
previousOffset = controller.offset;
|
||||
|
||||
// Release handle should stop scrolling.
|
||||
await gesture.up();
|
||||
// Last scheduled scroll.
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
previousOffset = controller.offset;
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.offset, previousOffset);
|
||||
});
|
||||
|
||||
testWidgets('keyboard selection should auto scroll - vertical', (WidgetTester tester) async {
|
||||
final FocusNode node = FocusNode();
|
||||
final ScrollController controller = ScrollController();
|
||||
|
@ -298,6 +298,67 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('dragging handle or selecting word triggers haptic feedback on Android', (WidgetTester tester) async {
|
||||
final List<MethodCall> log = <MethodCall>[];
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
|
||||
log.add(methodCall);
|
||||
return null;
|
||||
});
|
||||
addTearDown(() {
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall);
|
||||
});
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: SelectableRegion(
|
||||
focusNode: FocusNode(),
|
||||
selectionControls: materialTextSelectionControls,
|
||||
child: const Text('How are you?'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you?'), matching: find.byType(RichText)));
|
||||
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 6)); // at the 'r'
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
await gesture.up();
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
// `are` is selected.
|
||||
expect(paragraph.selections[0], const TextSelection(baseOffset: 4, extentOffset: 7));
|
||||
expect(
|
||||
log.last,
|
||||
isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.selectionClick'),
|
||||
);
|
||||
log.clear();
|
||||
final List<TextBox> boxes = paragraph.getBoxesForSelection(paragraph.selections[0]);
|
||||
expect(boxes.length, 1);
|
||||
final Offset handlePos = globalize(boxes[0].toRect().bottomRight, paragraph);
|
||||
await gesture.down(handlePos);
|
||||
final Offset endPos = Offset(textOffsetToPosition(paragraph, 8).dx, handlePos.dy);
|
||||
|
||||
// Select 1 more character by dragging end handle to trigger feedback.
|
||||
await gesture.moveTo(endPos);
|
||||
expect(paragraph.selections[0], const TextSelection(baseOffset: 4, extentOffset: 8));
|
||||
// Only Android vibrate when dragging the handle.
|
||||
switch(defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
expect(
|
||||
log.last,
|
||||
isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.selectionClick'),
|
||||
);
|
||||
break;
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.macOS:
|
||||
case TargetPlatform.windows:
|
||||
expect(log, isEmpty);
|
||||
break;
|
||||
}
|
||||
await gesture.up();
|
||||
}, variant: TargetPlatformVariant.all());
|
||||
|
||||
group('SelectionArea integration', () {
|
||||
testWidgets('mouse can select single text', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
|
Loading…
x
Reference in New Issue
Block a user