How can I perform a scale (pinch to zoom) multi finger gesture in Flutter's widget tests?
Asked Answered
T

4

9

I am writing widget tests for a widget that handles actions when the user performs a scale/zooming gesture on it by instantiating a GestureDetector with the onScaleUpdate property callback. I know how to perform drag, taps and long presses in widget tests, but I cannot find a way to perform scale gestures in widget tests.

I have tried several approaches, such as performing simultaneous drags on opposite directions:

final myWidget = find.byKey(const Key("myWidget"));
await tester.drag(myWidget, Offset(100, 0));
await tester.drag(myWidget, Offset(-100, 0));

but the drags can't happen simultaneously, the framework forces me to await until a drag is finished before performing the second drag.

Is there any way to perform scaling / pinch-to-zoom / multi finger gestures in widget tests?

Trueblue answered 8/3, 2020 at 15:33 Comment(0)
C
1

You can use createGesture() or startGesture() methods of WidgetTester to create two or more touches and control them. Here is an example:

    final widgetFinder = find.byKey(ValueKey('Scalable widget'));
    final center = tester.getCenter(widgetFinder);
    
    // create two touches:
    final touch1 = await tester.startGesture(center.translate(-10, 0));
    final touch2 = await tester.startGesture(center.translate(10, 0));

    // zoom in:
    await touch1.moveBy(Offset(-100, 0));
    await touch2.moveBy(Offset(100, 0));
    await tester.pump();
    
    // zoom out:
    await touch1.moveBy(Offset(10, 0));
    await touch2.moveBy(Offset(-10, 0));
    await tester.pump();
    
    // cancel touches:
    await touch1.cancel();
    await touch2.cancel();
Cyclamate answered 25/12, 2020 at 15:14 Comment(0)
S
0

This is how you can do it:

// Create the finder
final interactiveViewFinder = find.byType(InteractiveViewer);

// Get the center      
final center = tester.getCenter(interactiveViewFinder);

// Zoom in:
final Offset scaleStart1 = center;
final Offset scaleStart2 = Offset(center.dx + 10.0, center.dy);
final Offset scaleEnd1 = Offset(center.dx - 10.0, center.dy);
final Offset scaleEnd2 = Offset(center.dx + 10.0, center.dy);
final TestGesture gesture = await tester.createGesture();
final TestGesture gesture2 = await tester.createGesture();
await gesture.down(scaleStart1);
await gesture2.down(scaleStart2);
await tester.pump();
await gesture.moveTo(scaleEnd1);
await gesture2.moveTo(scaleEnd2);
await tester.pump();
await gesture.up();
await gesture2.up();
await tester.pumpAndSettle();

Source: Flutter's own unit tests for InteractiveViewer: https://github.com/flutter/flutter/blob/7d368dcf0c00b45fef5b02c5cccb8aa5306234ba/packages/flutter/test/widgets/interactive_viewer_test.dart#L49

Squama answered 24/9, 2021 at 19:32 Comment(0)
P
0

you can use this package pinch_zoom: ^1.0.0

Palindrome answered 26/1, 2022 at 8:55 Comment(0)
T
0

In case you need timed zoom, I wrote extension with timedZoomFrom method, similar to timedDragFrom of WidgetController.

extension ZoomTesting on WidgetTester{
  Future<void> timedZoomFrom(
      Offset startLocation1,
      Offset offset1,
      Offset startLocation2,
      Offset offset2,
      Duration duration, {
        int? pointer,
        int buttons = kPrimaryButton,
        double frequency = 60.0,
      }) {
    assert(frequency > 0);
    final int intervals = duration.inMicroseconds * frequency ~/ 1E6;
    assert(intervals > 1);
    pointer ??= nextPointer;
    int pointer2 = pointer + 1;
    final List<Duration> timeStamps = <Duration>[
      for (int t = 0; t <= intervals; t += 1)
        duration * t ~/ intervals,
    ];
    final List<Offset> offsets1 = <Offset>[
      startLocation1,
      for (int t = 0; t <= intervals; t += 1)
        startLocation1 + offset1 * (t / intervals),
    ];
    final List<Offset> offsets2 = <Offset>[
      startLocation2,
      for (int t = 0; t <= intervals; t += 1)
        startLocation2 + offset2 * (t / intervals),
    ];
    final List<PointerEventRecord> records = <PointerEventRecord>[
      PointerEventRecord(Duration.zero, <PointerEvent>[
        PointerAddedEvent(
          position: startLocation1,
        ),
        PointerAddedEvent(
          position: startLocation2,
        ),
        PointerDownEvent(
          position: startLocation1,
          pointer: pointer,
          buttons: buttons,
        ),
        PointerDownEvent(
          position: startLocation2,
          pointer: pointer2,
          buttons: buttons,
        ),
      ]),
      ...<PointerEventRecord>[
        for(int t = 0; t <= intervals; t += 1)
          PointerEventRecord(timeStamps[t], <PointerEvent>[
            PointerMoveEvent(
              timeStamp: timeStamps[t],
              position: offsets1[t+1],
              delta: offsets1[t+1] - offsets1[t],
              pointer: pointer,
              buttons: buttons,
            ),
            PointerMoveEvent(
              timeStamp: timeStamps[t],
              position: offsets2[t+1],
              delta: offsets2[t+1] - offsets2[t],
              pointer: pointer2,
              buttons: buttons,
            ),
          ]),
      ],
      PointerEventRecord(duration, <PointerEvent>[
        PointerUpEvent(
          timeStamp: duration,
          position: offsets1.last,
          pointer: pointer,
        ),
        PointerUpEvent(
          timeStamp: duration,
          position: offsets2.last,
          pointer: pointer2,
        ),
      ]),
    ];
    return TestAsyncUtils.guard<void>(() async {
      await handlePointerEventRecord(records);
    });
  }
}

Using example:

  // fast zoom in
  await tester.timedZoomFrom(
      center.translate(-10, 0), const Offset(-100, 0),
      center.translate(10, 0), const Offset(100, 0),
      const Duration(seconds: 1));
Tab answered 19/1, 2023 at 11:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.