blob: 7f433146fbb471bc62cc5bfe53f9f76e6fddf3f5 [file] [log] [blame]
// 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 '../rendering/mock_canvas.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)}');
}
}