visibility_test.dart 16.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/rendering.dart';
6 7 8 9 10 11 12
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

import '../rendering/mock_canvas.dart';
import 'semantics_tester.dart';

class TestState extends StatefulWidget {
13
  const TestState({ super.key, required this.child, required this.log });
14 15 16
  final Widget child;
  final List<String> log;
  @override
17
  State<TestState> createState() => _TestStateState();
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
}

class _TestStateState extends State<TestState> {
  @override
  void initState() {
    super.initState();
    widget.log.add('created new state');
  }
  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

void main() {
  testWidgets('Visibility', (WidgetTester tester) async {
34
    final SemanticsTester semantics = SemanticsTester(tester);
35 36
    final List<String> log = <String>[];

37
    final Widget testChild = GestureDetector(
38
      onTap: () { log.add('tap'); },
39
      child: Builder(
40 41
        builder: (BuildContext context) {
          final bool animating = TickerMode.of(context);
42
          return TestState(
43
            log: log,
44
            child: Text('a $animating', textDirection: TextDirection.rtl),
45 46 47 48 49 50
          );
        },
      ),
    );

    final Matcher expectedSemanticsWhenPresent = hasSemantics(
51
      TestSemantics.root(
52
        children: <TestSemantics>[
53
          TestSemantics.rootChild(
54 55 56
            label: 'a true',
            textDirection: TextDirection.rtl,
            actions: <SemanticsAction>[SemanticsAction.tap],
57
          ),
58 59 60 61 62 63 64
        ],
      ),
      ignoreId: true,
      ignoreRect: true,
      ignoreTransform: true,
    );

65
    final Matcher expectedSemanticsWhenAbsent = hasSemantics(TestSemantics.root());
66 67

    // We now run a sequence of pumpWidget calls one after the other. In
68
    // addition to verifying that the right behavior is seen in each case, this
69 70
    // also verifies that the widget can dynamically change from state to state.

71
    await tester.pumpWidget(Visibility(child: testChild));
72 73 74 75 76 77 78 79 80 81
    expect(find.byType(Text, skipOffstage: false), findsOneWidget);
    expect(find.text('a true', skipOffstage: false), findsOneWidget);
    expect(find.byType(Visibility), paints..paragraph());
    expect(tester.getSize(find.byType(Visibility)), const Size(800.0, 600.0));
    expect(semantics, expectedSemanticsWhenPresent);
    expect(log, <String>['created new state']);
    await tester.tap(find.byType(Visibility));
    expect(log, <String>['created new state', 'tap']);
    log.clear();

82
    await tester.pumpWidget(Visibility(visible: false, child: testChild));
83 84 85 86 87
    expect(find.byType(Text, skipOffstage: false), findsNothing);
    expect(find.byType(Visibility), paintsNothing);
    expect(tester.getSize(find.byType(Visibility)), const Size(800.0, 600.0));
    expect(semantics, expectedSemanticsWhenAbsent);
    expect(log, <String>[]);
88
    await tester.tap(find.byType(Visibility), warnIfMissed: false);
89 90 91
    expect(log, <String>[]);
    log.clear();

92 93 94
    await tester.pumpWidget(Center(
      child: Visibility(
        visible: false,
95
        child: testChild,
96
      ),
97
    ));
98 99 100 101 102 103
    expect(find.byType(Text, skipOffstage: false), findsNothing);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paintsNothing);
    expect(tester.getSize(find.byType(Visibility)), Size.zero);
    expect(semantics, expectedSemanticsWhenAbsent);
    expect(log, <String>[]);
104
    await tester.tap(find.byType(Visibility), warnIfMissed: false);
105 106 107
    expect(log, <String>[]);
    log.clear();

108 109 110 111
    await tester.pumpWidget(Center(
      child: Visibility(
        replacement: const Placeholder(),
        visible: false,
112
        child: testChild,
113
      ),
114
    ));
115 116 117 118 119 120
    expect(find.byType(Text, skipOffstage: false), findsNothing);
    expect(find.byType(Placeholder), findsOneWidget);
    expect(find.byType(Visibility), paints..path());
    expect(tester.getSize(find.byType(Visibility)), const Size(800.0, 600.0));
    expect(semantics, expectedSemanticsWhenAbsent);
    expect(log, <String>[]);
121
    await tester.tap(find.byType(Visibility), warnIfMissed: false);
122 123 124
    expect(log, <String>[]);
    log.clear();

125 126 127
    await tester.pumpWidget(Center(
      child: Visibility(
        replacement: const Placeholder(),
128
        child: testChild,
129
      ),
130
    ));
131 132 133 134 135 136 137 138 139 140 141
    expect(find.byType(Text, skipOffstage: false), findsOneWidget);
    expect(find.text('a true', skipOffstage: false), findsOneWidget);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paints..paragraph());
    expect(tester.getSize(find.byType(Visibility)), const Size(84.0, 14.0));
    expect(semantics, expectedSemanticsWhenPresent);
    expect(log, <String>['created new state']);
    await tester.tap(find.byType(Visibility));
    expect(log, <String>['created new state', 'tap']);
    log.clear();

142 143 144 145 146 147 148
    await tester.pumpWidget(Center(
      child: Visibility(
        maintainState: true,
        maintainAnimation: true,
        maintainSize: true,
        maintainInteractivity: true,
        maintainSemantics: true,
149
        child: testChild,
150
      ),
151
    ));
152 153 154 155 156 157 158 159 160 161 162
    expect(find.byType(Text, skipOffstage: false), findsOneWidget);
    expect(find.text('a true', skipOffstage: false), findsOneWidget);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paints..paragraph());
    expect(tester.getSize(find.byType(Visibility)), const Size(84.0, 14.0));
    expect(semantics, expectedSemanticsWhenPresent);
    expect(log, <String>['created new state']);
    await tester.tap(find.byType(Visibility));
    expect(log, <String>['created new state', 'tap']);
    log.clear();

163 164 165 166 167 168 169 170
    await tester.pumpWidget(Center(
      child: Visibility(
        visible: false,
        maintainState: true,
        maintainAnimation: true,
        maintainSize: true,
        maintainInteractivity: true,
        maintainSemantics: true,
171
        child: testChild,
172
      ),
173
    ));
174 175 176 177 178 179 180 181 182 183 184
    expect(find.byType(Text, skipOffstage: false), findsOneWidget);
    expect(find.text('a true', skipOffstage: false), findsOneWidget);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paintsNothing);
    expect(tester.getSize(find.byType(Visibility)), const Size(84.0, 14.0));
    expect(semantics, expectedSemanticsWhenPresent);
    expect(log, <String>[]);
    await tester.tap(find.byType(Visibility));
    expect(log, <String>['tap']);
    log.clear();

185 186 187 188 189 190 191
    await tester.pumpWidget(Center(
      child: Visibility(
        visible: false,
        maintainState: true,
        maintainAnimation: true,
        maintainSize: true,
        maintainInteractivity: true,
192
        child: testChild,
193
      ),
194
    ));
195 196 197 198 199 200 201 202 203 204 205
    expect(find.byType(Text, skipOffstage: false), findsOneWidget);
    expect(find.text('a true', skipOffstage: false), findsOneWidget);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paintsNothing);
    expect(tester.getSize(find.byType(Visibility)), const Size(84.0, 14.0));
    expect(semantics, expectedSemanticsWhenAbsent);
    expect(log, <String>[]);
    await tester.tap(find.byType(Visibility));
    expect(log, <String>['tap']);
    log.clear();

206 207 208 209 210 211 212
    await tester.pumpWidget(Center(
      child: Visibility(
        visible: false,
        maintainState: true,
        maintainAnimation: true,
        maintainSize: true,
        maintainSemantics: true,
213
        child: testChild,
214
      ),
215
    ));
216 217 218 219 220 221 222
    expect(find.byType(Text, skipOffstage: false), findsOneWidget);
    expect(find.text('a true', skipOffstage: false), findsOneWidget);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paintsNothing);
    expect(tester.getSize(find.byType(Visibility)), const Size(84.0, 14.0));
    expect(semantics, expectedSemanticsWhenPresent);
    expect(log, <String>['created new state']);
223
    await tester.tap(find.byType(Visibility), warnIfMissed: false);
224 225 226
    expect(log, <String>['created new state']);
    log.clear();

227 228 229 230 231 232
    await tester.pumpWidget(Center(
      child: Visibility(
        visible: false,
        maintainState: true,
        maintainAnimation: true,
        maintainSize: true,
233
        child: testChild,
234
      ),
235
    ));
236 237 238 239 240 241 242
    expect(find.byType(Text, skipOffstage: false), findsOneWidget);
    expect(find.text('a true', skipOffstage: false), findsOneWidget);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paintsNothing);
    expect(tester.getSize(find.byType(Visibility)), const Size(84.0, 14.0));
    expect(semantics, expectedSemanticsWhenAbsent);
    expect(log, <String>[]);
243
    await tester.tap(find.byType(Visibility), warnIfMissed: false);
244 245 246
    expect(log, <String>[]);
    log.clear();

247 248 249 250 251
    await tester.pumpWidget(Center(
      child: Visibility(
        visible: false,
        maintainState: true,
        maintainAnimation: true,
252
        child: testChild,
253
      ),
254
    ));
255
    expect(find.byType(Text, skipOffstage: false), findsOneWidget);
256
    expect(find.byType(Text), findsNothing);
257 258 259 260 261 262
    expect(find.text('a true', skipOffstage: false), findsOneWidget);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paintsNothing);
    expect(tester.getSize(find.byType(Visibility)), Size.zero);
    expect(semantics, expectedSemanticsWhenAbsent);
    expect(log, <String>['created new state']);
263
    await tester.tap(find.byType(Visibility), warnIfMissed: false);
264 265 266
    expect(log, <String>['created new state']);
    log.clear();

267 268 269 270
    await tester.pumpWidget(Center(
      child: Visibility(
        visible: false,
        maintainState: true,
271
        child: testChild,
272
      ),
273
    ));
274
    expect(find.byType(Text, skipOffstage: false), findsOneWidget);
275
    expect(find.byType(Text), findsNothing);
276 277 278 279 280 281
    expect(find.text('a false', skipOffstage: false), findsOneWidget);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paintsNothing);
    expect(tester.getSize(find.byType(Visibility)), Size.zero);
    expect(semantics, expectedSemanticsWhenAbsent);
    expect(log, <String>['created new state']);
282
    await tester.tap(find.byType(Visibility), warnIfMissed: false);
283 284 285 286 287
    expect(log, <String>['created new state']);
    log.clear();

    // Now we toggle the visibility off and on a few times to make sure that works.

288 289 290
    await tester.pumpWidget(Center(
      child: Visibility(
        maintainState: true,
291
        child: testChild,
292
      ),
293
    ));
294 295 296 297 298 299 300
    expect(find.byType(Text), findsOneWidget);
    expect(find.text('a true', skipOffstage: false), findsOneWidget);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paints..paragraph());
    expect(tester.getSize(find.byType(Visibility)), const Size(84.0, 14.0));
    expect(semantics, expectedSemanticsWhenPresent);
    expect(log, <String>[]);
301
    await tester.tap(find.byType(Visibility), warnIfMissed: false);
302 303 304
    expect(log, <String>['tap']);
    log.clear();

305 306 307 308
    await tester.pumpWidget(Center(
      child: Visibility(
        visible: false,
        maintainState: true,
309
        child: testChild,
310
      ),
311
    ));
312
    expect(find.byType(Text, skipOffstage: false), findsOneWidget);
313
    expect(find.byType(Text), findsNothing);
314 315 316 317 318 319
    expect(find.text('a false', skipOffstage: false), findsOneWidget);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paintsNothing);
    expect(tester.getSize(find.byType(Visibility)), Size.zero);
    expect(semantics, expectedSemanticsWhenAbsent);
    expect(log, <String>[]);
320
    await tester.tap(find.byType(Visibility), warnIfMissed: false);
321 322 323
    expect(log, <String>[]);
    log.clear();

324 325 326
    await tester.pumpWidget(Center(
      child: Visibility(
        maintainState: true,
327
        child: testChild,
328
      ),
329
    ));
330 331 332 333 334 335 336 337 338 339 340
    expect(find.byType(Text), findsOneWidget);
    expect(find.text('a true', skipOffstage: false), findsOneWidget);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paints..paragraph());
    expect(tester.getSize(find.byType(Visibility)), const Size(84.0, 14.0));
    expect(semantics, expectedSemanticsWhenPresent);
    expect(log, <String>[]);
    await tester.tap(find.byType(Visibility));
    expect(log, <String>['tap']);
    log.clear();

341 342 343 344
    await tester.pumpWidget(Center(
      child: Visibility(
        visible: false,
        maintainState: true,
345
        child: testChild,
346
      ),
347
    ));
348
    expect(find.byType(Text, skipOffstage: false), findsOneWidget);
349
    expect(find.byType(Text), findsNothing);
350 351 352 353 354 355
    expect(find.text('a false', skipOffstage: false), findsOneWidget);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paintsNothing);
    expect(tester.getSize(find.byType(Visibility)), Size.zero);
    expect(semantics, expectedSemanticsWhenAbsent);
    expect(log, <String>[]);
356
    await tester.tap(find.byType(Visibility), warnIfMissed: false);
357 358 359 360 361
    expect(log, <String>[]);
    log.clear();

    // Same but without maintainState.

362 363 364
    await tester.pumpWidget(Center(
      child: Visibility(
        visible: false,
365
        child: testChild,
366
      ),
367
    ));
368 369 370 371 372 373
    expect(find.byType(Text, skipOffstage: false), findsNothing);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paintsNothing);
    expect(tester.getSize(find.byType(Visibility)), Size.zero);
    expect(semantics, expectedSemanticsWhenAbsent);
    expect(log, <String>[]);
374
    await tester.tap(find.byType(Visibility), warnIfMissed: false);
375 376 377
    expect(log, <String>[]);
    log.clear();

378 379
    await tester.pumpWidget(Center(
      child: Visibility(
380
        child: testChild,
381
      ),
382
    ));
383 384 385 386 387 388 389 390 391 392 393
    expect(find.byType(Text), findsOneWidget);
    expect(find.text('a true', skipOffstage: false), findsOneWidget);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paints..paragraph());
    expect(tester.getSize(find.byType(Visibility)), const Size(84.0, 14.0));
    expect(semantics, expectedSemanticsWhenPresent);
    expect(log, <String>['created new state']);
    await tester.tap(find.byType(Visibility));
    expect(log, <String>['created new state', 'tap']);
    log.clear();

394 395 396
    await tester.pumpWidget(Center(
      child: Visibility(
        visible: false,
397
        child: testChild,
398
      ),
399
    ));
400 401 402 403 404 405
    expect(find.byType(Text, skipOffstage: false), findsNothing);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paintsNothing);
    expect(tester.getSize(find.byType(Visibility)), Size.zero);
    expect(semantics, expectedSemanticsWhenAbsent);
    expect(log, <String>[]);
406
    await tester.tap(find.byType(Visibility), warnIfMissed: false);
407 408 409
    expect(log, <String>[]);
    log.clear();

410 411
    await tester.pumpWidget(Center(
      child: Visibility(
412
        child: testChild,
413
      ),
414
    ));
415 416 417 418 419 420 421 422 423 424 425 426
    expect(find.byType(Text), findsOneWidget);
    expect(find.text('a true', skipOffstage: false), findsOneWidget);
    expect(find.byType(Placeholder), findsNothing);
    expect(find.byType(Visibility), paints..paragraph());
    expect(tester.getSize(find.byType(Visibility)), const Size(84.0, 14.0));
    expect(semantics, expectedSemanticsWhenPresent);
    expect(log, <String>['created new state']);
    await tester.tap(find.byType(Visibility));
    expect(log, <String>['created new state', 'tap']);
    log.clear();

    semantics.dispose();
427
  });
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473

  testWidgets('Visibility does not force compositing when visible and maintain*', (WidgetTester tester) async {
    await tester.pumpWidget(
      const Visibility(
        maintainSize: true,
        maintainAnimation: true,
        maintainState: true,
        child: Text('hello', textDirection: TextDirection.ltr),
      ),
    );

    // Root transform from the tester and then the picture created by the text.
    expect(tester.layers, hasLength(2));
    expect(tester.layers, isNot(contains(isA<OpacityLayer>())));
    expect(tester.layers.last, isA<PictureLayer>());
  });

  testWidgets('SliverVisibility does not force compositing when visible and maintain*', (WidgetTester tester) async {
    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
        child: CustomScrollView(
          slivers: <Widget>[
            SliverVisibility(
              maintainSize: true,
              maintainAnimation: true,
              maintainState: true,
              sliver: SliverList(
              delegate: SliverChildListDelegate.fixed(
                addRepaintBoundaries: false,
                <Widget>[
                  Text('hello'),
                ],
              ),
            ))
          ]
        ),
      ),
    );

    // This requires a lot more layers due to including sliver lists which do manage additional
    // offset layers. Just trust me this is one fewer layers than before...
    expect(tester.layers, hasLength(6));
    expect(tester.layers, isNot(contains(isA<OpacityLayer>())));
    expect(tester.layers.last, isA<PictureLayer>());
  });
474
}