| // 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 'package:flutter/rendering.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import 'semantics_tester.dart'; |
| |
| class TestState extends StatefulWidget { |
| const TestState({ super.key, required this.child, required this.log }); |
| final Widget child; |
| final List<String> log; |
| @override |
| State<TestState> createState() => _TestStateState(); |
| } |
| |
| 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 { |
| final SemanticsTester semantics = SemanticsTester(tester); |
| final List<String> log = <String>[]; |
| |
| final Widget testChild = GestureDetector( |
| onTap: () { log.add('tap'); }, |
| child: Builder( |
| builder: (BuildContext context) { |
| final bool animating = TickerMode.of(context); |
| return TestState( |
| log: log, |
| child: Text('a $animating', textDirection: TextDirection.rtl), |
| ); |
| }, |
| ), |
| ); |
| |
| final Matcher expectedSemanticsWhenPresent = hasSemantics( |
| TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics.rootChild( |
| label: 'a true', |
| textDirection: TextDirection.rtl, |
| actions: <SemanticsAction>[SemanticsAction.tap], |
| ), |
| ], |
| ), |
| ignoreId: true, |
| ignoreRect: true, |
| ignoreTransform: true, |
| ); |
| |
| final Matcher expectedSemanticsWhenPresentWithIgnorePointer = hasSemantics( |
| TestSemantics.root( |
| children: <TestSemantics>[ |
| TestSemantics.rootChild( |
| label: 'a true', |
| textDirection: TextDirection.rtl, |
| ), |
| ], |
| ), |
| ignoreId: true, |
| ignoreRect: true, |
| ignoreTransform: true, |
| ); |
| |
| final Matcher expectedSemanticsWhenAbsent = hasSemantics(TestSemantics.root()); |
| |
| // We now run a sequence of pumpWidget calls one after the other. In |
| // addition to verifying that the right behavior is seen in each case, this |
| // also verifies that the widget can dynamically change from state to state. |
| |
| await tester.pumpWidget(Visibility(child: testChild)); |
| 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(); |
| |
| await tester.pumpWidget(Visibility(visible: false, child: testChild)); |
| 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>[]); |
| await tester.tap(find.byType(Visibility), warnIfMissed: false); |
| expect(log, <String>[]); |
| log.clear(); |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| visible: false, |
| child: testChild, |
| ), |
| )); |
| 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>[]); |
| await tester.tap(find.byType(Visibility), warnIfMissed: false); |
| expect(log, <String>[]); |
| log.clear(); |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| replacement: const Placeholder(), |
| visible: false, |
| child: testChild, |
| ), |
| )); |
| 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>[]); |
| await tester.tap(find.byType(Visibility), warnIfMissed: false); |
| expect(log, <String>[]); |
| log.clear(); |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| replacement: const Placeholder(), |
| child: testChild, |
| ), |
| )); |
| 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(); |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| maintainState: true, |
| maintainAnimation: true, |
| maintainSize: true, |
| maintainInteractivity: true, |
| maintainSemantics: true, |
| child: testChild, |
| ), |
| )); |
| 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(); |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| visible: false, |
| maintainState: true, |
| maintainAnimation: true, |
| maintainSize: true, |
| maintainInteractivity: true, |
| maintainSemantics: true, |
| child: testChild, |
| ), |
| )); |
| 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(); |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| visible: false, |
| maintainState: true, |
| maintainAnimation: true, |
| maintainSize: true, |
| maintainInteractivity: true, |
| child: testChild, |
| ), |
| )); |
| 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(); |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| visible: false, |
| maintainState: true, |
| maintainAnimation: true, |
| maintainSize: true, |
| maintainSemantics: true, |
| child: testChild, |
| ), |
| )); |
| 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, expectedSemanticsWhenPresentWithIgnorePointer); |
| expect(log, <String>[]); |
| await tester.tap(find.byType(Visibility), warnIfMissed: false); |
| expect(log, <String>[]); |
| log.clear(); |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| visible: false, |
| maintainState: true, |
| maintainAnimation: true, |
| maintainSize: true, |
| child: testChild, |
| ), |
| )); |
| 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), warnIfMissed: false); |
| expect(log, <String>[]); |
| log.clear(); |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| visible: false, |
| maintainState: true, |
| maintainAnimation: true, |
| child: testChild, |
| ), |
| )); |
| expect(find.byType(Text, skipOffstage: false), findsOneWidget); |
| expect(find.byType(Text), findsNothing); |
| 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']); |
| await tester.tap(find.byType(Visibility), warnIfMissed: false); |
| expect(log, <String>['created new state']); |
| log.clear(); |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| visible: false, |
| maintainState: true, |
| child: testChild, |
| ), |
| )); |
| expect(find.byType(Text, skipOffstage: false), findsOneWidget); |
| expect(find.byType(Text), findsNothing); |
| 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']); |
| await tester.tap(find.byType(Visibility), warnIfMissed: false); |
| expect(log, <String>['created new state']); |
| log.clear(); |
| |
| // Now we toggle the visibility off and on a few times to make sure that works. |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| maintainState: true, |
| child: testChild, |
| ), |
| )); |
| 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), warnIfMissed: false); |
| expect(log, <String>['tap']); |
| log.clear(); |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| visible: false, |
| maintainState: true, |
| child: testChild, |
| ), |
| )); |
| expect(find.byType(Text, skipOffstage: false), findsOneWidget); |
| expect(find.byType(Text), findsNothing); |
| 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>[]); |
| await tester.tap(find.byType(Visibility), warnIfMissed: false); |
| expect(log, <String>[]); |
| log.clear(); |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| maintainState: true, |
| child: testChild, |
| ), |
| )); |
| 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(); |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| visible: false, |
| maintainState: true, |
| child: testChild, |
| ), |
| )); |
| expect(find.byType(Text, skipOffstage: false), findsOneWidget); |
| expect(find.byType(Text), findsNothing); |
| 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>[]); |
| await tester.tap(find.byType(Visibility), warnIfMissed: false); |
| expect(log, <String>[]); |
| log.clear(); |
| |
| // Same but without maintainState. |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| visible: false, |
| child: testChild, |
| ), |
| )); |
| 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>[]); |
| await tester.tap(find.byType(Visibility), warnIfMissed: false); |
| expect(log, <String>[]); |
| log.clear(); |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| child: testChild, |
| ), |
| )); |
| 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(); |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| visible: false, |
| child: testChild, |
| ), |
| )); |
| 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>[]); |
| await tester.tap(find.byType(Visibility), warnIfMissed: false); |
| expect(log, <String>[]); |
| log.clear(); |
| |
| await tester.pumpWidget(Center( |
| child: Visibility( |
| child: testChild, |
| ), |
| )); |
| 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(); |
| }); |
| |
| 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>()); |
| }); |
| |
| testWidgets('Visibility.of returns correct value', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| const Directionality( |
| textDirection: TextDirection.ltr, |
| child: _ShowVisibility(), |
| ), |
| ); |
| expect(find.text('is visible ? true', skipOffstage: false), findsOneWidget); |
| |
| await tester.pumpWidget( |
| const Directionality( |
| textDirection: TextDirection.ltr, |
| child: Visibility( |
| maintainState: true, |
| child: _ShowVisibility(), |
| ), |
| ), |
| ); |
| expect(find.text('is visible ? true', skipOffstage: false), findsOneWidget); |
| |
| await tester.pumpWidget( |
| const Directionality( |
| textDirection: TextDirection.ltr, |
| child: Visibility( |
| visible: false, |
| maintainState: true, |
| child: _ShowVisibility(), |
| ), |
| ), |
| ); |
| expect(find.text('is visible ? false', skipOffstage: false), findsOneWidget); |
| }); |
| |
| testWidgets('Visibility.of works when multiple Visibility widgets are in hierarchy', (WidgetTester tester) async { |
| bool didChangeDependencies = false; |
| void handleDidChangeDependencies() { |
| didChangeDependencies = true; |
| } |
| |
| Widget newWidget({required bool ancestorIsVisible, required bool descendantIsVisible}) { |
| return Directionality( |
| textDirection: TextDirection.ltr, |
| child: Visibility( |
| visible: ancestorIsVisible, |
| maintainState: true, |
| child: Center( |
| child: Visibility( |
| visible: descendantIsVisible, |
| maintainState: true, |
| child: _ShowVisibility( |
| onDidChangeDependencies: handleDidChangeDependencies, |
| ), |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(newWidget(ancestorIsVisible: true, descendantIsVisible: true)); |
| expect(didChangeDependencies, isTrue); |
| expect(find.text('is visible ? true', skipOffstage: false), findsOneWidget); |
| didChangeDependencies = false; |
| |
| await tester.pumpWidget(newWidget(ancestorIsVisible: true, descendantIsVisible: false)); |
| expect(didChangeDependencies, isTrue); |
| expect(find.text('is visible ? false', skipOffstage: false), findsOneWidget); |
| didChangeDependencies = false; |
| |
| await tester.pumpWidget(newWidget(ancestorIsVisible: true, descendantIsVisible: false)); |
| expect(didChangeDependencies, isFalse); |
| |
| await tester.pumpWidget(newWidget(ancestorIsVisible: false, descendantIsVisible: false)); |
| expect(didChangeDependencies, isTrue); |
| didChangeDependencies = false; |
| |
| await tester.pumpWidget(newWidget(ancestorIsVisible: false, descendantIsVisible: true)); |
| expect(didChangeDependencies, isTrue); |
| expect(find.text('is visible ? false', skipOffstage: false), findsOneWidget); |
| }); |
| } |
| |
| class _ShowVisibility extends StatefulWidget { |
| const _ShowVisibility({this.onDidChangeDependencies}); |
| |
| final VoidCallback? onDidChangeDependencies; |
| |
| @override |
| State<_ShowVisibility> createState() => _ShowVisibilityState(); |
| } |
| |
| class _ShowVisibilityState extends State<_ShowVisibility> { |
| @override |
| void didChangeDependencies() { |
| super.didChangeDependencies(); |
| if (widget.onDidChangeDependencies != null) { |
| widget.onDidChangeDependencies!(); |
| } |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return Text('is visible ? ${Visibility.of(context)}'); |
| } |
| } |