live_widget_controller_test.dart 5.96 KB
Newer Older
1 2 3 4
// 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.

5
import 'package:flutter/foundation.dart';
6
import 'package:flutter/gestures.dart';
7
import 'package:flutter/material.dart';
8 9 10
import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';

11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
// This test is very fragile and bypasses some zone-related checks.
// It is written this way to verify some invariants that would otherwise
// be difficult to check.
// Do not use this test as a guide for writing good Flutter code.

class TestBinding extends WidgetsFlutterBinding {
  @override
  void initInstances() {
    super.initInstances();
    _instance = this;
  }

  @override
  bool debugCheckZone(String entryPoint) { return true; }

  static TestBinding get instance => BindingBase.checkInstance(_instance);
  static TestBinding? _instance;

  static TestBinding ensureInitialized() {
    if (TestBinding._instance == null) {
      TestBinding();
    }
    return TestBinding.instance;
  }
}

37
class CountButton extends StatefulWidget {
38
  const CountButton({super.key});
39

40
  @override
41
  State<CountButton> createState() => _CountButtonState();
42 43 44 45 46 47
}

class _CountButtonState extends State<CountButton> {
  int counter = 0;
  @override
  Widget build(BuildContext context) {
48
    return ElevatedButton(
49 50 51 52 53 54 55 56 57 58
      child: Text('Counter $counter'),
      onPressed: () {
        setState(() {
          counter += 1;
        });
      },
    );
  }
}

59
class AnimateSample extends StatefulWidget {
60
  const AnimateSample({super.key});
61

62
  @override
63
  State<AnimateSample> createState() => _AnimateSampleState();
64 65 66 67
}

class _AnimateSampleState extends State<AnimateSample>
    with SingleTickerProviderStateMixin {
68
  late AnimationController _controller;
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    )..forward();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (BuildContext context, _) => Text('Value: ${_controller.value}'),
    );
  }
}

94
void main() {
95 96
  TestBinding.ensureInitialized();

97
  test('Test pump on LiveWidgetController', () async {
98
    runApp(const MaterialApp(home: Center(child: CountButton())));
99

100
    await SchedulerBinding.instance.endOfFrame;
101
    final WidgetController controller =
102
        LiveWidgetController(WidgetsBinding.instance);
103 104 105 106 107 108 109 110
    await controller.tap(find.text('Counter 0'));
    expect(find.text('Counter 0'), findsOneWidget);
    expect(find.text('Counter 1'), findsNothing);
    await controller.pump();
    expect(find.text('Counter 0'), findsNothing);
    expect(find.text('Counter 1'), findsOneWidget);
  });

111
  test('Test pumpAndSettle on LiveWidgetController', () async {
112
    runApp(const MaterialApp(home: Center(child: AnimateSample())));
113
    await SchedulerBinding.instance.endOfFrame;
114
    final WidgetController controller =
115
        LiveWidgetController(WidgetsBinding.instance);
116 117 118 119 120
    expect(find.text('Value: 1.0'), findsNothing);
    await controller.pumpAndSettle();
    expect(find.text('Value: 1.0'), findsOneWidget);
  });

121 122 123 124 125 126 127 128 129 130 131 132
  test('Input event array on LiveWidgetController', () async {
    final List<String> logs = <String>[];
    runApp(
      MaterialApp(
        home: Listener(
          onPointerDown: (PointerDownEvent event) => logs.add('down ${event.buttons}'),
          onPointerMove: (PointerMoveEvent event) => logs.add('move ${event.buttons}'),
          onPointerUp: (PointerUpEvent event) => logs.add('up ${event.buttons}'),
          child: const Text('test'),
        ),
      ),
    );
133
    await SchedulerBinding.instance.endOfFrame;
134
    final WidgetController controller =
135
        LiveWidgetController(WidgetsBinding.instance);
136 137 138 139 140

    final Offset location = controller.getCenter(find.text('test'));
    final List<PointerEventRecord> records = <PointerEventRecord>[
      PointerEventRecord(Duration.zero, <PointerEvent>[
        // Typically PointerAddedEvent is not used in testers, but for records
141
        // captured on a device it is usually what starts a gesture.
142 143 144 145 146 147 148 149 150 151 152
        PointerAddedEvent(
          position: location,
        ),
        PointerDownEvent(
          position: location,
          buttons: kSecondaryMouseButton,
          pointer: 1,
        ),
      ]),
      ...<PointerEventRecord>[
        for (Duration t = const Duration(milliseconds: 5);
153 154
             t < const Duration(milliseconds: 80);
             t += const Duration(milliseconds: 16))
155 156 157 158 159 160
          PointerEventRecord(t, <PointerEvent>[
            PointerMoveEvent(
              timeStamp: t - const Duration(milliseconds: 1),
              position: location,
              buttons: kSecondaryMouseButton,
              pointer: 1,
161 162
            ),
          ]),
163 164 165 166 167 168 169
      ],
      PointerEventRecord(const Duration(milliseconds: 80), <PointerEvent>[
        PointerUpEvent(
          timeStamp: const Duration(milliseconds: 79),
          position: location,
          buttons: kSecondaryMouseButton,
          pointer: 1,
170 171
        ),
      ]),
172 173 174 175 176 177 178
    ];
    final List<Duration> timeDiffs =
        await controller.handlePointerEventRecord(records);

    expect(timeDiffs.length, records.length);
    for (final Duration diff in timeDiffs) {
      // Allow some freedom of time delay in real world.
Lioness100's avatar
Lioness100 committed
179
      // TODO(pdblasi-google): The expected wiggle room should be -1, but occasional
180 181 182
      // results were reaching -6. This assert has been adjusted to reduce flakiness,
      // but the root cause is still unknown. (https://github.com/flutter/flutter/issues/109638)
      assert(diff.inMilliseconds > -7, 'timeDiffs were: $timeDiffs (offending time was ${diff.inMilliseconds}ms)');
183 184 185 186 187 188 189 190 191 192
    }

    const String b = '$kSecondaryMouseButton';
    expect(logs.first, 'down $b');
    for (int i = 1; i < logs.length - 1; i++) {
      expect(logs[i], 'move $b');
    }
    expect(logs.last, 'up $b');
  });
}