gesture_binding_resample_event_test.dart 6.18 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:ui' as ui;

import 'package:clock/clock.dart';
import 'package:fake_async/fake_async.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/scheduler.dart';
12
import 'package:flutter_test/flutter_test.dart';
13 14 15 16 17 18 19 20 21 22 23

typedef HandleEventCallback = void Function(PointerEvent event);

class TestResampleEventFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding {
  HandleEventCallback? callback;
  FrameCallback? postFrameCallback;
  Duration? frameTime;

  @override
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    super.handleEvent(event, entry);
24
    if (callback != null) {
25
      callback?.call(event);
26
    }
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
  }

  @override
  Duration get currentSystemFrameTimeStamp {
    assert(frameTime != null);
    return frameTime!;
  }

  @override
  int addPostFrameCallback(FrameCallback callback) {
    postFrameCallback = callback;
    return 0;
  }

  @override
  SamplingClock? get debugSamplingClock => TestSamplingClock();
}

class TestSamplingClock implements SamplingClock {
  @override
  DateTime now() => clock.now();

  @override
  Stopwatch stopwatch() => clock.stopwatch();
}

typedef ResampleEventTest = void Function(FakeAsync async);

void testResampleEvent(String description, ResampleEventTest callback) {
  test(description, () {
    fakeAsync((FakeAsync async) {
      callback(async);
59
    }, initialTime: DateTime.utc(2015));
60 61
  }, skip: isBrowser); // https://github.com/flutter/flutter/issues/87067
                       // Fake clock is not working with the web platform.
62 63 64 65 66 67 68 69 70 71 72
}

void main() {
  final TestResampleEventFlutterBinding binding = TestResampleEventFlutterBinding();
  testResampleEvent('Pointer event resampling', (FakeAsync async) {
    Duration currentTime() => Duration(milliseconds: clock.now().millisecondsSinceEpoch);
    final Duration epoch = currentTime();
    final ui.PointerDataPacket packet = ui.PointerDataPacket(
      data: <ui.PointerData>[
        ui.PointerData(
            change: ui.PointerChange.add,
73
            timeStamp: epoch,
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
        ),
        ui.PointerData(
            change: ui.PointerChange.down,
            timeStamp: epoch + const Duration(milliseconds: 10),
        ),
        ui.PointerData(
            change: ui.PointerChange.move,
            physicalX: 10.0,
            timeStamp: epoch + const Duration(milliseconds: 20),
        ),
        ui.PointerData(
            change: ui.PointerChange.move,
            physicalX: 20.0,
            timeStamp: epoch + const Duration(milliseconds: 30),
        ),
        ui.PointerData(
            change: ui.PointerChange.move,
            physicalX: 30.0,
            timeStamp: epoch + const Duration(milliseconds: 40),
        ),
        ui.PointerData(
            change: ui.PointerChange.move,
            physicalX: 40.0,
            timeStamp: epoch + const Duration(milliseconds: 50),
        ),
        ui.PointerData(
            change: ui.PointerChange.move,
            physicalX: 50.0,
            timeStamp: epoch + const Duration(milliseconds: 60),
        ),
        ui.PointerData(
            change: ui.PointerChange.up,
            physicalX: 50.0,
            timeStamp: epoch + const Duration(milliseconds: 70),
        ),
        ui.PointerData(
            change: ui.PointerChange.remove,
            physicalX: 50.0,
            timeStamp: epoch + const Duration(milliseconds: 70),
        ),
      ],
    );

    const Duration samplingOffset = Duration(milliseconds: -5);
    const Duration frameInterval = Duration(microseconds: 16667);

120 121
    GestureBinding.instance.resamplingEnabled = true;
    GestureBinding.instance.samplingOffset = samplingOffset;
122 123 124 125

    final List<PointerEvent> events = <PointerEvent>[];
    binding.callback = events.add;

126
    GestureBinding.instance.platformDispatcher.onPointerDataPacket?.call(packet);
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142

    // No pointer events should have been dispatched yet.
    expect(events.length, 0);

    // Frame callback should have been requested.
    FrameCallback? callback = binding.postFrameCallback;
    binding.postFrameCallback = null;
    expect(callback, isNotNull);

    binding.frameTime = epoch + const Duration(milliseconds: 15);
    callback!(Duration.zero);

    // One pointer event should have been dispatched.
    expect(events.length, 1);
    expect(events[0], isA<PointerDownEvent>());
    expect(events[0].timeStamp, binding.frameTime! + samplingOffset);
143
    expect(events[0].position, Offset(0.0 / _devicePixelRatio, 0.0));
144 145 146 147 148 149 150 151 152 153 154 155 156 157

    // Second frame callback should have been requested.
    callback = binding.postFrameCallback;
    binding.postFrameCallback = null;
    expect(callback, isNotNull);

    final Duration frameTime = epoch + const Duration(milliseconds: 25);
    binding.frameTime = frameTime;
    callback!(Duration.zero);

    // Second pointer event should have been dispatched.
    expect(events.length, 2);
    expect(events[1], isA<PointerMoveEvent>());
    expect(events[1].timeStamp, binding.frameTime! + samplingOffset);
158 159
    expect(events[1].position, Offset(10.0 / _devicePixelRatio, 0.0));
    expect(events[1].delta, Offset(10.0 / _devicePixelRatio, 0.0));
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182

    // Verify that resampling continues without a frame callback.
    async.elapse(frameInterval * 1.5);

    // Third pointer event should have been dispatched.
    expect(events.length, 3);
    expect(events[2], isA<PointerMoveEvent>());
    expect(events[2].timeStamp, frameTime + frameInterval + samplingOffset);

    async.elapse(frameInterval);

    // Remaining pointer events should have been dispatched.
    expect(events.length, 5);
    expect(events[3], isA<PointerMoveEvent>());
    expect(events[3].timeStamp, frameTime + frameInterval * 2 + samplingOffset);
    expect(events[4], isA<PointerUpEvent>());
    expect(events[4].timeStamp, frameTime + frameInterval * 2 + samplingOffset);

    async.elapse(frameInterval);

    // No more pointer events should have been dispatched.
    expect(events.length, 5);

183
    GestureBinding.instance.resamplingEnabled = false;
184 185
  });
}
186 187

double get _devicePixelRatio => GestureBinding.instance.platformDispatcher.implicitView!.devicePixelRatio;