Skip to content
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

Detect when tap target is covered #61

Merged
merged 27 commits into from
Jun 27, 2024
Merged
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3e45cc3
Add optimized search for interactable widget positions
danielmolnar Jun 21, 2024
ac9a157
Add test "Finds widgets after dragging down and up"
danielmolnar Jun 21, 2024
7f6dfb8
Add print warning
danielmolnar Jun 21, 2024
a79007f
Add CustomTappableArea
danielmolnar Jun 22, 2024
e4c8bb4
Add test "Partially covered, finds tappable area"
danielmolnar Jun 22, 2024
6a00a46
Add MeasureSize PokeTestWidget
danielmolnar Jun 22, 2024
bc1de17
Adjust test to setup
danielmolnar Jun 22, 2024
a6d7389
Add test Poke test widget throws without defined tappable spots
danielmolnar Jun 22, 2024
3cce03c
Add docs to PokeTestWidget
danielmolnar Jun 22, 2024
6657c76
Improve test naming, constellation
danielmolnar Jun 24, 2024
c919bf1
Make captureConsoleOutput public
danielmolnar Jun 20, 2024
ef71574
Add test "Warn about using and finding alternative tappable area."
danielmolnar Jun 24, 2024
7df7e4f
Add todo
danielmolnar Jun 24, 2024
1f18195
Refactor act
danielmolnar Jun 24, 2024
f5df08d
Return HitTestFailure if no position is found
danielmolnar Jun 24, 2024
4ca1113
Fix test not running on flutter 3.10
danielmolnar Jun 24, 2024
9faf92c
Improve naming
danielmolnar Jun 24, 2024
7b4ca4d
Use positioned.fill instead of measure size
danielmolnar Jun 25, 2024
717b842
Create a useful error message when an InkWell covers the tap target
passsy Jun 25, 2024
1d93a41
Report useful error when tap target has 0x0 pixels in size
passsy Jun 26, 2024
531121f
Revert "Return HitTestFailure if no position is found"
danielmolnar Jun 26, 2024
b42d443
Improve getting the location of a widget in code using public APIs :t…
passsy Jun 27, 2024
8210279
Merge branch 'detect-pokable-positions' into tap-text-in-elevated-button
passsy Jun 27, 2024
3eff69e
Merge remote-tracking branch 'origin/main' into tap-text-in-elevated-…
passsy Jun 27, 2024
dc63f28
Delete MeasureSize
passsy Jun 27, 2024
f28a83c
Cleanup
passsy Jun 27, 2024
ac9b6d3
Print the actual widget, not the selector in tap error messages
passsy Jun 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor act
danielmolnar committed Jun 24, 2024
commit 1f18195f91bb616e781d004b790980e5f7d93662
118 changes: 56 additions & 62 deletions lib/src/act/act.dart
Original file line number Diff line number Diff line change
@@ -78,29 +78,33 @@ class Act {
return TestAsyncUtils.guard<void>(() async {
return _alwaysPropagateDevicePointerEvents(() async {
final renderBox = _getRenderBoxOrThrow(selector);
_validateViewBounds(renderBox, selector: selector);

final tappedPosition = _findPokablePosition(
widgetSelector: selector,
snapshot: snapshot,
) ??
renderBox.localToGlobal(renderBox.size.center(Offset.zero));

// Before tapping the widget, we need to make sure that the widget is
// not covered by another widget, or outside the viewport.
_validatePokePosition(
position: tappedPosition,
target: renderBox,
// not outside the viewport or covered by another widget.
_validateViewBounds(renderBox, selector: selector);
final positionToTap = _findPokablePosition(
widgetSelector: selector,
snapshot: snapshot,
);

if (positionToTap == null) {
final centerPosition =
renderBox.localToGlobal(renderBox.size.center(Offset.zero));
_throwHitTestFailureReport(
position: centerPosition,
target: renderBox,
snapshot: snapshot,
);
return;
}

final binding = TestWidgetsFlutterBinding.instance;

// Finally, tap the widget by sending a down and up event.
final downEvent = PointerDownEvent(position: tappedPosition);
final downEvent = PointerDownEvent(position: positionToTap);
binding.handlePointerEvent(downEvent);

final upEvent = PointerUpEvent(position: tappedPosition);
final upEvent = PointerUpEvent(position: positionToTap);
binding.handlePointerEvent(upEvent);

await binding.pump();
@@ -135,67 +139,67 @@ class Act {
Duration duration = const Duration(milliseconds: 50),
}) {
// Check if widget is in the widget tree. Throws if not.
dragStart.snapshot().existsOnce();
final snapshot = dragStart.snapshot()..existsOnce();

return TestAsyncUtils.guard<void>(() async {
return _alwaysPropagateDevicePointerEvents(() async {
final renderBox = _getRenderBoxOrThrow(dragStart);

final binding = TestWidgetsFlutterBinding.instance;
final snapShot = dragStart.snapshot();

// Before dragging, we need to make sure that `dragStart` is
// not outside the viewport or covered by another widget.
_validateViewBounds(renderBox, selector: dragStart);
final dragPosition = _findPokablePosition(
widgetSelector: dragStart,
snapshot: snapShot,
snapshot: snapshot,
);

// This will throw an accurate error about why the center is not
// interactable
if (dragPosition == null) {
final centerPosition =
renderBox.localToGlobal(renderBox.size.center(Offset.zero));
_validatePokePosition(
_throwHitTestFailureReport(
position: centerPosition,
target: renderBox,
snapshot: snapShot,
snapshot: snapshot,
);
} else {
final targetName = dragTarget.toStringBreadcrumb();

bool isTargetVisible() {
final renderObject = _renderObjectFromSelector(dragTarget);
if (renderObject is RenderBox) {
return _validateViewBounds(
renderObject,
selector: dragTarget,
throwIfInvisible: false,
);
} else {
return false;
}
return;
}
final targetName = dragTarget.toStringBreadcrumb();

bool isTargetVisible() {
final renderObject = _renderObjectFromSelector(dragTarget);
if (renderObject is RenderBox) {
return _validateViewBounds(
renderObject,
selector: dragTarget,
throwIfInvisible: false,
);
} else {
return false;
}
}

bool isVisible = isTargetVisible();
bool isVisible = isTargetVisible();

if (isVisible) {
return;
}
if (isVisible) {
return;
}

int iterations = 0;
while (iterations < maxIteration && !isVisible) {
await gestures.drag(dragPosition, moveStep);
await binding.pump(duration);
iterations++;
isVisible = isTargetVisible();
}
int iterations = 0;
while (iterations < maxIteration && !isVisible) {
await gestures.drag(dragPosition, moveStep);
await binding.pump(duration);
iterations++;
isVisible = isTargetVisible();
}

final totalDragged = moveStep * iterations.toDouble();
final totalDragged = moveStep * iterations.toDouble();

if (!isVisible) {
throw TestFailure(
"$targetName is not visible after dragging $iterations times and a total dragged offset of $totalDragged.",
);
}
if (!isVisible) {
throw TestFailure(
"$targetName is not visible after dragging $iterations times and a total dragged offset of $totalDragged.",
);
}
});
});
@@ -257,7 +261,7 @@ class Act {
/// Checks if the widget is visible and not covered by another widget
///
/// This test fails when the widget does not react to hit tests
void _validatePokePosition({
void _throwHitTestFailureReport({
required Offset position,
required RenderObject target,
required WidgetSnapshot snapshot,
@@ -270,14 +274,6 @@ class Act {
binding.hitTest(result, position);
final hitTestEntries = result.path.toList();

// Check if [target] received the tap event
for (final HitTestEntry entry in hitTestEntries) {
if (entry.target == target) {
// Success, target was hit by hitTest
return;
}
}

final List<Element> hitTargetElements =
hitTestEntries.mapNotNull((e) => e.element).toList();

@@ -426,8 +422,6 @@ class Act {
}

/// Checks if the widget is visible and not covered by another widget
///
/// This test fails when the widget does not react to hit tests
bool _canBePoked({
required Offset position,
required RenderObject target,