modal_barrier_test.dart 7.64 KB
Newer Older
yjbanov's avatar
yjbanov committed
1 2 3 4 5
// Copyright 2016 The Chromium 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 'package:flutter_test/flutter_test.dart';
6
import 'package:flutter/foundation.dart';
yjbanov's avatar
yjbanov committed
7
import 'package:flutter/material.dart';
8
import 'package:flutter/rendering.dart';
yjbanov's avatar
yjbanov committed
9 10
import 'package:flutter/widgets.dart';

11 12
import 'semantics_tester.dart';

yjbanov's avatar
yjbanov committed
13 14 15 16 17 18
void main() {
  bool tapped;
  Widget tapTarget;

  setUp(() {
    tapped = false;
19
    tapTarget = GestureDetector(
yjbanov's avatar
yjbanov committed
20 21 22
      onTap: () {
        tapped = true;
      },
23
      child: const SizedBox(
yjbanov's avatar
yjbanov committed
24 25
        width: 10.0,
        height: 10.0,
26 27
        child: Text('target', textDirection: TextDirection.ltr),
      ),
yjbanov's avatar
yjbanov committed
28 29 30
    );
  });

31
  testWidgets('ModalBarrier prevents interactions with widgets behind it', (WidgetTester tester) async {
32
    final Widget subject = Stack(
33
      textDirection: TextDirection.ltr,
34 35
      children: <Widget>[
        tapTarget,
36
        const ModalBarrier(dismissible: false),
37
      ],
38
    );
yjbanov's avatar
yjbanov committed
39

40 41 42
    await tester.pumpWidget(subject);
    await tester.tap(find.text('target'));
    await tester.pumpWidget(subject);
43 44
    expect(tapped, isFalse,
      reason: 'because the tap is prevented by ModalBarrier');
yjbanov's avatar
yjbanov committed
45 46
  });

47
  testWidgets('ModalBarrier does not prevent interactions with widgets in front of it', (WidgetTester tester) async {
48
    final Widget subject = Stack(
49
      textDirection: TextDirection.ltr,
50
      children: <Widget>[
51
        const ModalBarrier(dismissible: false),
52
        tapTarget,
53
      ],
54
    );
yjbanov's avatar
yjbanov committed
55

56 57 58
    await tester.pumpWidget(subject);
    await tester.tap(find.text('target'));
    await tester.pumpWidget(subject);
59 60
    expect(tapped, isTrue,
      reason: 'because the tap is not prevented by ModalBarrier');
yjbanov's avatar
yjbanov committed
61 62
  });

63
  testWidgets('ModalBarrier pops the Navigator when dismissed', (WidgetTester tester) async {
64
    final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
65 66
      '/': (BuildContext context) => FirstWidget(),
      '/modal': (BuildContext context) => SecondWidget(),
67
    };
yjbanov's avatar
yjbanov committed
68

69
    await tester.pumpWidget(MaterialApp(routes: routes));
yjbanov's avatar
yjbanov committed
70

71 72
    // Initially the barrier is not visible
    expect(find.byKey(const ValueKey<String>('barrier')), findsNothing);
yjbanov's avatar
yjbanov committed
73

74
    // Tapping on X routes to the barrier
75
    await tester.tap(find.text('X'));
76 77
    await tester.pump(); // begin transition
    await tester.pump(const Duration(seconds: 1)); // end transition
yjbanov's avatar
yjbanov committed
78

79
    // Tap on the barrier to dismiss it
80
    await tester.tap(find.byKey(const ValueKey<String>('barrier')));
81 82
    await tester.pump(); // begin transition
    await tester.pump(const Duration(seconds: 1)); // end transition
yjbanov's avatar
yjbanov committed
83

84
    expect(find.byKey(const ValueKey<String>('barrier')), findsNothing,
85
      reason: 'The route should have been dismissed by tapping the barrier.');
yjbanov's avatar
yjbanov committed
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 121 122 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
  testWidgets('ModalBarrier does not pop the Navigator with a WillPopScope that returns false', (WidgetTester tester) async {
    bool willPopCalled = false;
    final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
      '/': (BuildContext context) => FirstWidget(),
      '/modal': (BuildContext context) => Stack(
        children: <Widget>[
          SecondWidget(),
          WillPopScope(
            child: const SizedBox(),
            onWillPop: () async {
              willPopCalled = true;
              return false;
            },
          ),
      ],),
    };

    await tester.pumpWidget(MaterialApp(routes: routes));

    // Initially the barrier is not visible
    expect(find.byKey(const ValueKey<String>('barrier')), findsNothing);

    // Tapping on X routes to the barrier
    await tester.tap(find.text('X'));
    await tester.pump(); // begin transition
    await tester.pump(const Duration(seconds: 1)); // end transition

    expect(willPopCalled, isFalse);

    // Tap on the barrier to attempt to dismiss it
    await tester.tap(find.byKey(const ValueKey<String>('barrier')));
    await tester.pump(); // begin transition
    await tester.pump(const Duration(seconds: 1)); // end transition

    expect(find.byKey(const ValueKey<String>('barrier')), findsOneWidget,
      reason: 'The route should still be present if the pop is vetoed.');

    expect(willPopCalled, isTrue);
  });

  testWidgets('ModalBarrier pops the Navigator with a WillPopScope that returns true', (WidgetTester tester) async {
    bool willPopCalled = false;
    final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
      '/': (BuildContext context) => FirstWidget(),
      '/modal': (BuildContext context) => Stack(
        children: <Widget>[
          SecondWidget(),
          WillPopScope(
            child: const SizedBox(),
            onWillPop: () async {
              willPopCalled = true;
              return true;
            },
          ),
        ],),
    };

    await tester.pumpWidget(MaterialApp(routes: routes));

    // Initially the barrier is not visible
    expect(find.byKey(const ValueKey<String>('barrier')), findsNothing);

    // Tapping on X routes to the barrier
    await tester.tap(find.text('X'));
    await tester.pump(); // begin transition
    await tester.pump(const Duration(seconds: 1)); // end transition

    expect(willPopCalled, isFalse);

    // Tap on the barrier to attempt to dismiss it
    await tester.tap(find.byKey(const ValueKey<String>('barrier')));
    await tester.pump(); // begin transition
    await tester.pump(const Duration(seconds: 1)); // end transition

    expect(find.byKey(const ValueKey<String>('barrier')), findsNothing,
      reason: 'The route should not be present if the pop is permitted.');

    expect(willPopCalled, isTrue);
  });

168
  testWidgets('Undismissible ModalBarrier hidden in semantic tree', (WidgetTester tester) async {
169
    final SemanticsTester semantics = SemanticsTester(tester);
170 171
    await tester.pumpWidget(const ModalBarrier(dismissible: false));

172
    final TestSemantics expectedSemantics = TestSemantics.root();
173 174 175 176 177
    expect(semantics, hasSemantics(expectedSemantics));

    semantics.dispose();
  });

178 179 180
  testWidgets('Dismissible ModalBarrier includes button in semantic tree on iOS', (WidgetTester tester) async {
    debugDefaultTargetPlatformOverride = TargetPlatform.iOS;

181
    final SemanticsTester semantics = SemanticsTester(tester);
182 183
    await tester.pumpWidget(const Directionality(
      textDirection: TextDirection.ltr,
184
      child: ModalBarrier(
185 186 187 188
        dismissible: true,
        semanticsLabel: 'Dismiss',
      ),
    ));
189

190
    final TestSemantics expectedSemantics = TestSemantics.root(
191
      children: <TestSemantics>[
192
        TestSemantics.rootChild(
193
          rect: TestSemantics.fullScreen,
194 195 196
          actions: SemanticsAction.tap.index,
          label: 'Dismiss',
          textDirection: TextDirection.ltr,
197 198 199
        ),
      ]
    );
200
    expect(semantics, hasSemantics(expectedSemantics, ignoreId: true));
201

202 203 204 205 206
    semantics.dispose();
    debugDefaultTargetPlatformOverride = null;
  });

  testWidgets('Dismissible ModalBarrier is hidden on Android (back button is used to dismiss)', (WidgetTester tester) async {
207
    final SemanticsTester semantics = SemanticsTester(tester);
208 209
    await tester.pumpWidget(const ModalBarrier(dismissible: true));

210
    final TestSemantics expectedSemantics = TestSemantics.root();
211 212
    expect(semantics, hasSemantics(expectedSemantics));

213 214
    semantics.dispose();
  });
yjbanov's avatar
yjbanov committed
215 216
}

217
class FirstWidget extends StatelessWidget {
218
  @override
yjbanov's avatar
yjbanov committed
219
  Widget build(BuildContext context) {
220 221 222 223 224 225 226 227
    return GestureDetector(
      onTap: () {
        Navigator.pushNamed(context, '/modal');
      },
      child: Container(
        child: const Text('X'),
      ),
    );
yjbanov's avatar
yjbanov committed
228 229 230
  }
}

231
class SecondWidget extends StatelessWidget {
232
  @override
yjbanov's avatar
yjbanov committed
233
  Widget build(BuildContext context) {
234 235 236 237
    return const ModalBarrier(
      key: ValueKey<String>('barrier'),
      dismissible: true,
    );
yjbanov's avatar
yjbanov committed
238 239
  }
}