gesture_binding_resample_event_test.dart 6.13 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 24 25 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

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);
    if (callback != null)
      callback?.call(event);
  }

  @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);
    }, initialTime: DateTime.utc(2015, 1, 1));
59 60
  }, skip: isBrowser); // https://github.com/flutter/flutter/issues/87067
                       // Fake clock is not working with the web platform.
61 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,
            physicalX: 0.0,
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 120
        ),
        ui.PointerData(
            change: ui.PointerChange.down,
            physicalX: 0.0,
            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);

121 122
    GestureBinding.instance!.resamplingEnabled = true;
    GestureBinding.instance!.samplingOffset = samplingOffset;
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183

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

    ui.window.onPointerDataPacket?.call(packet);

    // 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);
    expect(events[0].position, Offset(0.0 / ui.window.devicePixelRatio, 0.0));

    // 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);
    expect(events[1].position, Offset(10.0 / ui.window.devicePixelRatio, 0.0));
    expect(events[1].delta, Offset(10.0 / ui.window.devicePixelRatio, 0.0));

    // 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);

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