mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Selecting an implementation widget with the on-device inspector opens the code location for the nearest project widget (#176530)
Fixes https://github.com/flutter/devtools/issues/9252 Previously, if a user selected a widget that was not created in their project files using the on-device inspector, we would open the code location for that widget. Now, we open the code location of the nearest ancestor widget in their project files. (e.g., a user selects `RichText` in the framework, we open the `Text` widget in their project) Users can still open the code location for a Flutter framework or third-party package widget by explicitly selecting it in the DevTools widget tree. See more details/rationale on issue comment https://github.com/flutter/devtools/issues/9252#issuecomment-3368491936 _Note: This resolves one of the top user complaints in the 2025 DevTools user survey._ 
This commit is contained in:
parent
3dbd33d3aa
commit
a50b9d56d4
@ -1613,21 +1613,36 @@ mixin WidgetInspectorService {
|
||||
switch (object) {
|
||||
case Element() when object != selection.currentElement:
|
||||
selection.currentElement = object;
|
||||
_sendInspectEvent(selection.currentElement);
|
||||
_notifyToolsOfSelection(selection.currentElement);
|
||||
return true;
|
||||
case RenderObject() when object != selection.current:
|
||||
selection.current = object;
|
||||
_sendInspectEvent(selection.current);
|
||||
_notifyToolsOfSelection(selection.current);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Notify attached tools to navigate to an object's source location.
|
||||
void _sendInspectEvent(Object? object) {
|
||||
/// Notify connected tools (e.g. Flutter DevTools, IDE plugins) that a new
|
||||
/// widget has been selected.
|
||||
///
|
||||
/// This method triggers two actions:
|
||||
/// 1. It calls [developer.inspect] on the provided [object], making it
|
||||
/// available for inspection in Flutter DevTools.
|
||||
/// 2. It posts a 'navigate' [ToolEvent] with the source code location of the
|
||||
/// selected widget, allowing IDEs to navigate to the corresponding file
|
||||
/// and line.
|
||||
///
|
||||
/// If [restrictToProjectFiles] is true and the selected widget is not from
|
||||
/// the local project (i.e., it's from the Flutter framework or a package),
|
||||
/// the 'navigate' event will point to the nearest ancestor widget that is
|
||||
/// part of the local project.
|
||||
void _notifyToolsOfSelection(Object? object, {bool restrictToProjectFiles = false}) {
|
||||
inspect(object);
|
||||
|
||||
final _Location? location = _getSelectedWidgetLocation();
|
||||
final _Location? location = _getSelectedWidgetLocation(
|
||||
restrictToSummaryTree: restrictToProjectFiles,
|
||||
);
|
||||
if (location != null) {
|
||||
postEvent('navigate', <String, Object>{
|
||||
'fileUri': location.file, // URI file path of the location.
|
||||
@ -2463,8 +2478,18 @@ mixin WidgetInspectorService {
|
||||
return _safeJsonEncode(_getSelectedSummaryWidget(null, groupName));
|
||||
}
|
||||
|
||||
_Location? _getSelectedWidgetLocation() {
|
||||
return _getCreationLocation(_getSelectedWidgetDiagnosticsNode(null)?.value);
|
||||
/// Returns the creation location of the currently selected widget.
|
||||
///
|
||||
/// If [restrictToSummaryTree] is true and the currently selected widget is
|
||||
/// not in the summary tree (i.e. not created by the current project), this
|
||||
/// method will instead return the location of its nearest ancestor widget
|
||||
/// that is in the summary tree.
|
||||
_Location? _getSelectedWidgetLocation({bool restrictToSummaryTree = false}) {
|
||||
final DiagnosticsNode? selectedNode = restrictToSummaryTree
|
||||
? _getSelectedSummaryDiagnosticsNode(null)
|
||||
: _getSelectedWidgetDiagnosticsNode(null);
|
||||
|
||||
return _getCreationLocation(selectedNode?.value);
|
||||
}
|
||||
|
||||
DiagnosticsNode? _getSelectedSummaryDiagnosticsNode(String? previousSelectionId) {
|
||||
@ -3019,7 +3044,10 @@ class _WidgetInspectorState extends State<WidgetInspector> with WidgetsBindingOb
|
||||
selection.clear();
|
||||
} else {
|
||||
// Otherwise notify DevTools of the current selection.
|
||||
WidgetInspectorService.instance._sendInspectEvent(selection.current);
|
||||
WidgetInspectorService.instance._notifyToolsOfSelection(
|
||||
selection.current,
|
||||
restrictToProjectFiles: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3029,7 +3057,10 @@ class _WidgetInspectorState extends State<WidgetInspector> with WidgetsBindingOb
|
||||
}
|
||||
if (_lastPointerLocation != null) {
|
||||
_inspectAt(_lastPointerLocation!);
|
||||
WidgetInspectorService.instance._sendInspectEvent(selection.current);
|
||||
WidgetInspectorService.instance._notifyToolsOfSelection(
|
||||
selection.current,
|
||||
restrictToProjectFiles: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -939,7 +939,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'WidgetInspector Exit Selection Mode button',
|
||||
'On-device selection test',
|
||||
(WidgetTester tester) async {
|
||||
// Enable widget selection mode.
|
||||
WidgetInspectorService.instance.isSelectMode = true;
|
||||
@ -947,23 +947,12 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
||||
final GlobalKey inspectorKey = GlobalKey();
|
||||
setupDefaultPubRootDirectory(service);
|
||||
|
||||
Widget exitWidgetSelectionButtonBuilder(
|
||||
BuildContext context, {
|
||||
required VoidCallback onPressed,
|
||||
required String semanticsLabel,
|
||||
required GlobalKey key,
|
||||
}) {
|
||||
return Material(
|
||||
child: ElevatedButton(onPressed: onPressed, key: key, child: null),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: WidgetInspector(
|
||||
key: inspectorKey,
|
||||
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
||||
exitWidgetSelectionButtonBuilder: null,
|
||||
tapBehaviorButtonBuilder: null,
|
||||
moveExitWidgetSelectionButtonBuilder: null,
|
||||
child: const Text('Child 1'),
|
||||
@ -991,286 +980,303 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
||||
final String file = event['fileUri']! as String;
|
||||
final int line = event['line']! as int;
|
||||
final int column = event['column']! as int;
|
||||
expect(file, endsWith('text.dart'));
|
||||
expect(file, endsWith('widget_inspector_test.dart'));
|
||||
// We don't hardcode the actual lines the widgets are created on as that
|
||||
// would make this test fragile.
|
||||
expect(line, isNotNull);
|
||||
// Column numbers are more stable than line numbers.
|
||||
expect(column, equals(16));
|
||||
expect(column, equals(28));
|
||||
},
|
||||
// [intended] Test requires --track-widget-creation flag.
|
||||
skip: !WidgetInspectorService.instance.isWidgetCreationTracked(),
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'[LTR] WidgetInspector Move Exit Selection Mode button to the right then left',
|
||||
(WidgetTester tester) async {
|
||||
WidgetInspectorService.instance.isSelectMode = true;
|
||||
final GlobalKey inspectorKey = GlobalKey();
|
||||
setupDefaultPubRootDirectory(service);
|
||||
|
||||
Widget exitWidgetSelectionButtonBuilder(
|
||||
BuildContext context, {
|
||||
required VoidCallback onPressed,
|
||||
required String semanticsLabel,
|
||||
required GlobalKey key,
|
||||
}) {
|
||||
return Material(
|
||||
child: ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
key: key,
|
||||
child: const Text('EXIT SELECT MODE'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget moveWidgetSelectionButtonBuilder(
|
||||
BuildContext context, {
|
||||
required VoidCallback onPressed,
|
||||
required String semanticsLabel,
|
||||
bool usesDefaultAlignment = true,
|
||||
}) {
|
||||
return Material(
|
||||
child: ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
child: Text(usesDefaultAlignment ? 'MOVE RIGHT' : 'MOVE LEFT'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Finder buttonFinder(String buttonText) {
|
||||
return find.ancestor(of: find.text(buttonText), matching: find.byType(ElevatedButton));
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: WidgetInspector(
|
||||
key: inspectorKey,
|
||||
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
||||
moveExitWidgetSelectionButtonBuilder: moveWidgetSelectionButtonBuilder,
|
||||
tapBehaviorButtonBuilder: null,
|
||||
child: const Text('APP'),
|
||||
),
|
||||
group('On-device inspector buttons', () {
|
||||
Widget exitWidgetSelectionButtonBuilder(
|
||||
BuildContext context, {
|
||||
required VoidCallback onPressed,
|
||||
required String semanticsLabel,
|
||||
required GlobalKey key,
|
||||
}) {
|
||||
return Material(
|
||||
child: ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
key: key,
|
||||
child: const Text('EXIT SELECT MODE'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final Finder exitButton = buttonFinder('EXIT SELECT MODE');
|
||||
expect(exitButton, findsOneWidget);
|
||||
final Finder moveRightButton = buttonFinder('MOVE RIGHT');
|
||||
expect(moveRightButton, findsOneWidget);
|
||||
final double initialExitButtonX = tester.getCenter(exitButton).dx;
|
||||
|
||||
await tester.tap(moveRightButton);
|
||||
await tester.pump();
|
||||
|
||||
final Finder moveLeftButton = buttonFinder('MOVE LEFT');
|
||||
expect(moveLeftButton, findsOneWidget);
|
||||
final double movedExitButtonX = tester.getCenter(exitButton).dx;
|
||||
|
||||
expect(initialExitButtonX, lessThan(movedExitButtonX), reason: 'LTR: should move right');
|
||||
|
||||
await tester.tap(moveLeftButton);
|
||||
await tester.pump();
|
||||
|
||||
final double finalExitButtonX = tester.getCenter(exitButton).dx;
|
||||
expect(finalExitButtonX, equals(initialExitButtonX));
|
||||
},
|
||||
// [intended] Test requires --track-widget-creation flag.
|
||||
skip: !WidgetInspectorService.instance.isWidgetCreationTracked(),
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'[RTL] WidgetInspector Move Exit Selection Mode button to the left then right',
|
||||
(WidgetTester tester) async {
|
||||
WidgetInspectorService.instance.isSelectMode = true;
|
||||
final GlobalKey inspectorKey = GlobalKey();
|
||||
setupDefaultPubRootDirectory(service);
|
||||
|
||||
Widget exitWidgetSelectionButtonBuilder(
|
||||
BuildContext context, {
|
||||
required VoidCallback onPressed,
|
||||
required String semanticsLabel,
|
||||
required GlobalKey key,
|
||||
}) {
|
||||
return Material(
|
||||
child: ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
key: key,
|
||||
child: const Text('EXIT SELECT MODE'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget moveWidgetSelectionButtonBuilder(
|
||||
BuildContext context, {
|
||||
required VoidCallback onPressed,
|
||||
required String semanticsLabel,
|
||||
bool usesDefaultAlignment = true,
|
||||
}) {
|
||||
return Material(
|
||||
child: ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
child: Text(usesDefaultAlignment ? 'MOVE RIGHT' : 'MOVE LEFT'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Finder buttonFinder(String buttonText) {
|
||||
return find.ancestor(of: find.text(buttonText), matching: find.byType(ElevatedButton));
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: WidgetInspector(
|
||||
key: inspectorKey,
|
||||
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
||||
moveExitWidgetSelectionButtonBuilder: moveWidgetSelectionButtonBuilder,
|
||||
tapBehaviorButtonBuilder: null,
|
||||
child: const Text('APP'),
|
||||
),
|
||||
Widget moveWidgetSelectionButtonBuilder(
|
||||
BuildContext context, {
|
||||
required VoidCallback onPressed,
|
||||
required String semanticsLabel,
|
||||
bool usesDefaultAlignment = true,
|
||||
}) {
|
||||
return Material(
|
||||
child: ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
child: Text(usesDefaultAlignment ? 'MOVE RIGHT' : 'MOVE LEFT'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final Finder exitButton = buttonFinder('EXIT SELECT MODE');
|
||||
expect(exitButton, findsOneWidget);
|
||||
final Finder moveRightButton = buttonFinder('MOVE RIGHT');
|
||||
expect(moveRightButton, findsOneWidget);
|
||||
final double initialExitButtonX = tester.getCenter(exitButton).dx;
|
||||
|
||||
await tester.tap(moveRightButton);
|
||||
await tester.pump();
|
||||
|
||||
final Finder moveLeftButton = buttonFinder('MOVE LEFT');
|
||||
expect(moveLeftButton, findsOneWidget);
|
||||
final double movedExitButtonX = tester.getCenter(exitButton).dx;
|
||||
|
||||
expect(initialExitButtonX, greaterThan(movedExitButtonX), reason: 'RTL: should move left');
|
||||
|
||||
await tester.tap(moveLeftButton);
|
||||
await tester.pump();
|
||||
|
||||
final double finalExitButtonX = tester.getCenter(exitButton).dx;
|
||||
expect(finalExitButtonX, equals(initialExitButtonX));
|
||||
},
|
||||
// [intended] Test requires --track-widget-creation flag.
|
||||
skip: !WidgetInspectorService.instance.isWidgetCreationTracked(),
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'WidgetInspector Tap behavior button',
|
||||
(WidgetTester tester) async {
|
||||
Widget exitWidgetSelectionButtonBuilder(
|
||||
BuildContext context, {
|
||||
required VoidCallback onPressed,
|
||||
required String semanticsLabel,
|
||||
required GlobalKey key,
|
||||
}) {
|
||||
return Material(
|
||||
child: ElevatedButton(onPressed: onPressed, key: key, child: null),
|
||||
);
|
||||
}
|
||||
|
||||
Widget tapBehaviorButtonBuilder(
|
||||
BuildContext context, {
|
||||
required VoidCallback onPressed,
|
||||
required String semanticsLabel,
|
||||
required bool selectionOnTapEnabled,
|
||||
}) {
|
||||
return Material(
|
||||
child: ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
child: Text(selectionOnTapEnabled ? 'SELECTION ON TAP' : 'APP INTERACTION ON TAP'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Finder buttonFinder(String buttonText) {
|
||||
return find.ancestor(of: find.text(buttonText), matching: find.byType(ElevatedButton));
|
||||
}
|
||||
|
||||
int navigateEventsCount() =>
|
||||
service.dispatchedEvents('navigate', stream: 'ToolEvent').length;
|
||||
|
||||
// Enable widget selection mode.
|
||||
WidgetInspectorService.instance.isSelectMode = true;
|
||||
|
||||
// Pump the test widget.
|
||||
final GlobalKey inspectorKey = GlobalKey();
|
||||
setupDefaultPubRootDirectory(service);
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: WidgetInspector(
|
||||
key: inspectorKey,
|
||||
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
||||
tapBehaviorButtonBuilder: tapBehaviorButtonBuilder,
|
||||
moveExitWidgetSelectionButtonBuilder: null,
|
||||
child: const Row(children: <Widget>[Text('Child 1'), Text('Child 2')]),
|
||||
),
|
||||
Widget tapBehaviorButtonBuilder(
|
||||
BuildContext context, {
|
||||
required VoidCallback onPressed,
|
||||
required String semanticsLabel,
|
||||
required bool selectionOnTapEnabled,
|
||||
}) {
|
||||
return Material(
|
||||
child: ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
child: Text(selectionOnTapEnabled ? 'SELECTION ON TAP' : 'APP INTERACTION ON TAP'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Verify there are no navigate events yet.
|
||||
expect(navigateEventsCount(), equals(0));
|
||||
Finder buttonFinder(String buttonText) {
|
||||
return find.ancestor(of: find.text(buttonText), matching: find.byType(ElevatedButton));
|
||||
}
|
||||
|
||||
// Tap on the first child widget.
|
||||
final Finder child1 = find.text('Child 1');
|
||||
await tester.tap(child1, warnIfMissed: false);
|
||||
await tester.pump();
|
||||
int navigateEventsCount() => service.dispatchedEvents('navigate', stream: 'ToolEvent').length;
|
||||
|
||||
// Verify the selection matches the first child widget.
|
||||
final Element child1Element = child1.evaluate().first;
|
||||
expect(service.selection.current, equals(child1Element.renderObject));
|
||||
testWidgets(
|
||||
'Exit select mode button',
|
||||
(WidgetTester tester) async {
|
||||
// Enable widget selection mode.
|
||||
WidgetInspectorService.instance.isSelectMode = true;
|
||||
|
||||
// Verify that a navigate event was sent.
|
||||
expect(navigateEventsCount(), equals(1));
|
||||
final GlobalKey inspectorKey = GlobalKey();
|
||||
setupDefaultPubRootDirectory(service);
|
||||
|
||||
// Tap on the SELECTION ON TAP button.
|
||||
final Finder tapBehaviorButtonBefore = buttonFinder('SELECTION ON TAP');
|
||||
expect(tapBehaviorButtonBefore, findsOneWidget);
|
||||
await tester.tap(tapBehaviorButtonBefore);
|
||||
await tester.pump();
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: WidgetInspector(
|
||||
key: inspectorKey,
|
||||
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
||||
tapBehaviorButtonBuilder: null,
|
||||
moveExitWidgetSelectionButtonBuilder: null,
|
||||
child: const Column(children: <Widget>[Text('Child 1'), Text('Child 2')]),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Verify the tap behavior button's UI has been updated.
|
||||
expect(tapBehaviorButtonBefore, findsNothing);
|
||||
final Finder tapBehaviorButtonAfter = buttonFinder('APP INTERACTION ON TAP');
|
||||
expect(tapBehaviorButtonAfter, findsOneWidget);
|
||||
// tap on child 1
|
||||
final Finder child1 = find.text('Child 1');
|
||||
await tester.tap(child1, warnIfMissed: false);
|
||||
await tester.pump();
|
||||
|
||||
// Tap on the second child widget.
|
||||
final Finder child2 = find.text('Child 2');
|
||||
await tester.tap(child2, warnIfMissed: false);
|
||||
await tester.pump();
|
||||
// ensure that developer.inspect was called on child 1
|
||||
expect(
|
||||
service.inspectedObjects(),
|
||||
equals(<RenderObject?>[child1.evaluate().first.renderObject]),
|
||||
);
|
||||
|
||||
// Verify there is no selection.
|
||||
expect(service.selection.current, isNull);
|
||||
// ensure that there was a single navigate event
|
||||
expect(navigateEventsCount(), equals(1));
|
||||
|
||||
// Verify no navigate events were sent.
|
||||
expect(navigateEventsCount(), equals(1));
|
||||
// tap the exit selection mode button
|
||||
final Finder exitButton = buttonFinder('EXIT SELECT MODE');
|
||||
expect(exitButton, findsOneWidget);
|
||||
await tester.tap(exitButton);
|
||||
await tester.pump();
|
||||
|
||||
// Tap on the SELECTION ON TAP button again.
|
||||
await tester.tap(tapBehaviorButtonAfter);
|
||||
await tester.pump();
|
||||
// tap on child 2
|
||||
final Finder child2 = find.text('Child 2');
|
||||
await tester.tap(child2, warnIfMissed: false);
|
||||
await tester.pump();
|
||||
|
||||
// Verify the tap behavior button's UI has been reset.
|
||||
expect(tapBehaviorButtonAfter, findsNothing);
|
||||
expect(tapBehaviorButtonBefore, findsOneWidget);
|
||||
// ensure that developer.inspect was still only called on child 1
|
||||
expect(
|
||||
service.inspectedObjects(),
|
||||
equals(<RenderObject?>[child1.evaluate().first.renderObject]),
|
||||
);
|
||||
|
||||
// Tap on the second child widget again.
|
||||
await tester.tap(child2, warnIfMissed: false);
|
||||
await tester.pump();
|
||||
// ensure that there is still only a single navigate event
|
||||
expect(navigateEventsCount(), equals(1));
|
||||
},
|
||||
// [intended] Test requires --track-widget-creation flag.
|
||||
skip: !WidgetInspectorService.instance.isWidgetCreationTracked(),
|
||||
);
|
||||
|
||||
// Verify the selection now matches the second child widget.
|
||||
final Element child2Element = child2.evaluate().first;
|
||||
expect(service.selection.current, equals(child2Element.renderObject));
|
||||
testWidgets(
|
||||
'[LTR] Move button group to the right then left',
|
||||
(WidgetTester tester) async {
|
||||
WidgetInspectorService.instance.isSelectMode = true;
|
||||
final GlobalKey inspectorKey = GlobalKey();
|
||||
setupDefaultPubRootDirectory(service);
|
||||
|
||||
// Verify another navigate event was sent.
|
||||
expect(navigateEventsCount(), equals(2));
|
||||
},
|
||||
// [intended] Test requires --track-widget-creation flag.
|
||||
skip: !WidgetInspectorService.instance.isWidgetCreationTracked(),
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: WidgetInspector(
|
||||
key: inspectorKey,
|
||||
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
||||
moveExitWidgetSelectionButtonBuilder: moveWidgetSelectionButtonBuilder,
|
||||
tapBehaviorButtonBuilder: null,
|
||||
child: const Text('APP'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final Finder exitButton = buttonFinder('EXIT SELECT MODE');
|
||||
expect(exitButton, findsOneWidget);
|
||||
final Finder moveRightButton = buttonFinder('MOVE RIGHT');
|
||||
expect(moveRightButton, findsOneWidget);
|
||||
final double initialExitButtonX = tester.getCenter(exitButton).dx;
|
||||
|
||||
await tester.tap(moveRightButton);
|
||||
await tester.pump();
|
||||
|
||||
final Finder moveLeftButton = buttonFinder('MOVE LEFT');
|
||||
expect(moveLeftButton, findsOneWidget);
|
||||
final double movedExitButtonX = tester.getCenter(exitButton).dx;
|
||||
|
||||
expect(initialExitButtonX, lessThan(movedExitButtonX), reason: 'LTR: should move right');
|
||||
|
||||
await tester.tap(moveLeftButton);
|
||||
await tester.pump();
|
||||
|
||||
final double finalExitButtonX = tester.getCenter(exitButton).dx;
|
||||
expect(finalExitButtonX, equals(initialExitButtonX));
|
||||
},
|
||||
// [intended] Test requires --track-widget-creation flag.
|
||||
skip: !WidgetInspectorService.instance.isWidgetCreationTracked(),
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'[RTL] Move button group to the left then right',
|
||||
(WidgetTester tester) async {
|
||||
WidgetInspectorService.instance.isSelectMode = true;
|
||||
final GlobalKey inspectorKey = GlobalKey();
|
||||
setupDefaultPubRootDirectory(service);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.rtl,
|
||||
child: WidgetInspector(
|
||||
key: inspectorKey,
|
||||
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
||||
moveExitWidgetSelectionButtonBuilder: moveWidgetSelectionButtonBuilder,
|
||||
tapBehaviorButtonBuilder: null,
|
||||
child: const Text('APP'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final Finder exitButton = buttonFinder('EXIT SELECT MODE');
|
||||
expect(exitButton, findsOneWidget);
|
||||
final Finder moveRightButton = buttonFinder('MOVE RIGHT');
|
||||
expect(moveRightButton, findsOneWidget);
|
||||
final double initialExitButtonX = tester.getCenter(exitButton).dx;
|
||||
|
||||
await tester.tap(moveRightButton);
|
||||
await tester.pump();
|
||||
|
||||
final Finder moveLeftButton = buttonFinder('MOVE LEFT');
|
||||
expect(moveLeftButton, findsOneWidget);
|
||||
final double movedExitButtonX = tester.getCenter(exitButton).dx;
|
||||
|
||||
expect(
|
||||
initialExitButtonX,
|
||||
greaterThan(movedExitButtonX),
|
||||
reason: 'RTL: should move left',
|
||||
);
|
||||
|
||||
await tester.tap(moveLeftButton);
|
||||
await tester.pump();
|
||||
|
||||
final double finalExitButtonX = tester.getCenter(exitButton).dx;
|
||||
expect(finalExitButtonX, equals(initialExitButtonX));
|
||||
},
|
||||
// [intended] Test requires --track-widget-creation flag.
|
||||
skip: !WidgetInspectorService.instance.isWidgetCreationTracked(),
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Tap behavior button',
|
||||
(WidgetTester tester) async {
|
||||
// Enable widget selection mode.
|
||||
WidgetInspectorService.instance.isSelectMode = true;
|
||||
|
||||
// Pump the test widget.
|
||||
final GlobalKey inspectorKey = GlobalKey();
|
||||
setupDefaultPubRootDirectory(service);
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: WidgetInspector(
|
||||
key: inspectorKey,
|
||||
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
||||
tapBehaviorButtonBuilder: tapBehaviorButtonBuilder,
|
||||
moveExitWidgetSelectionButtonBuilder: null,
|
||||
child: const Row(children: <Widget>[Text('Child 1'), Text('Child 2')]),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Verify there are no navigate events yet.
|
||||
expect(navigateEventsCount(), equals(0));
|
||||
|
||||
// Tap on the first child widget.
|
||||
final Finder child1 = find.text('Child 1');
|
||||
await tester.tap(child1, warnIfMissed: false);
|
||||
await tester.pump();
|
||||
|
||||
// Verify the selection matches the first child widget.
|
||||
final Element child1Element = child1.evaluate().first;
|
||||
expect(service.selection.current, equals(child1Element.renderObject));
|
||||
|
||||
// Verify that a navigate event was sent.
|
||||
expect(navigateEventsCount(), equals(1));
|
||||
|
||||
// Tap on the SELECTION ON TAP button.
|
||||
final Finder tapBehaviorButtonBefore = buttonFinder('SELECTION ON TAP');
|
||||
expect(tapBehaviorButtonBefore, findsOneWidget);
|
||||
await tester.tap(tapBehaviorButtonBefore);
|
||||
await tester.pump();
|
||||
|
||||
// Verify the tap behavior button's UI has been updated.
|
||||
expect(tapBehaviorButtonBefore, findsNothing);
|
||||
final Finder tapBehaviorButtonAfter = buttonFinder('APP INTERACTION ON TAP');
|
||||
expect(tapBehaviorButtonAfter, findsOneWidget);
|
||||
|
||||
// Tap on the second child widget.
|
||||
final Finder child2 = find.text('Child 2');
|
||||
await tester.tap(child2, warnIfMissed: false);
|
||||
await tester.pump();
|
||||
|
||||
// Verify there is no selection.
|
||||
expect(service.selection.current, isNull);
|
||||
|
||||
// Verify no navigate events were sent.
|
||||
expect(navigateEventsCount(), equals(1));
|
||||
|
||||
// Tap on the SELECTION ON TAP button again.
|
||||
await tester.tap(tapBehaviorButtonAfter);
|
||||
await tester.pump();
|
||||
|
||||
// Verify the tap behavior button's UI has been reset.
|
||||
expect(tapBehaviorButtonAfter, findsNothing);
|
||||
expect(tapBehaviorButtonBefore, findsOneWidget);
|
||||
|
||||
// Tap on the second child widget again.
|
||||
await tester.tap(child2, warnIfMissed: false);
|
||||
await tester.pump();
|
||||
|
||||
// Verify the selection now matches the second child widget.
|
||||
final Element child2Element = child2.evaluate().first;
|
||||
expect(service.selection.current, equals(child2Element.renderObject));
|
||||
|
||||
// Verify another navigate event was sent.
|
||||
expect(navigateEventsCount(), equals(2));
|
||||
},
|
||||
// [intended] Test requires --track-widget-creation flag.
|
||||
skip: !WidgetInspectorService.instance.isWidgetCreationTracked(),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('test transformDebugCreator will re-order if after stack trace', (
|
||||
WidgetTester tester,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user