blob: 30760c4874c83cbc94cf430904bd09ab77972680 [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/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Nested TickerMode cannot turn tickers back on', (WidgetTester tester) async {
int outerTickCount = 0;
int innerTickCount = 0;
Widget nestedTickerModes({required bool innerEnabled, required bool outerEnabled}) {
return Directionality(
textDirection: TextDirection.rtl,
child: TickerMode(
enabled: outerEnabled,
child: Row(
children: <Widget>[
_TickingWidget(
onTick: () {
outerTickCount++;
},
),
TickerMode(
enabled: innerEnabled,
child: _TickingWidget(
onTick: () {
innerTickCount++;
},
),
),
],
),
),
);
}
await tester.pumpWidget(
nestedTickerModes(
outerEnabled: false,
innerEnabled: true,
),
);
expect(outerTickCount, 0);
expect(innerTickCount, 0);
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
expect(outerTickCount, 0);
expect(innerTickCount, 0);
await tester.pumpWidget(
nestedTickerModes(
outerEnabled: true,
innerEnabled: false,
),
);
outerTickCount = 0;
innerTickCount = 0;
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
expect(outerTickCount, 4);
expect(innerTickCount, 0);
await tester.pumpWidget(
nestedTickerModes(
outerEnabled: true,
innerEnabled: true,
),
);
outerTickCount = 0;
innerTickCount = 0;
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
expect(outerTickCount, 4);
expect(innerTickCount, 4);
await tester.pumpWidget(
nestedTickerModes(
outerEnabled: false,
innerEnabled: false,
),
);
outerTickCount = 0;
innerTickCount = 0;
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
expect(outerTickCount, 0);
expect(innerTickCount, 0);
});
testWidgets('Changing TickerMode does not rebuild widgets with SingleTickerProviderStateMixin', (WidgetTester tester) async {
Widget widgetUnderTest({required bool tickerEnabled}) {
return TickerMode(
enabled: tickerEnabled,
child: const _TickingWidget(),
);
}
_TickingWidgetState state() => tester.state<_TickingWidgetState>(find.byType(_TickingWidget));
await tester.pumpWidget(widgetUnderTest(tickerEnabled: true));
expect(state().ticker.isTicking, isTrue);
expect(state().buildCount, 1);
await tester.pumpWidget(widgetUnderTest(tickerEnabled: false));
expect(state().ticker.isTicking, isFalse);
expect(state().buildCount, 1);
await tester.pumpWidget(widgetUnderTest(tickerEnabled: true));
expect(state().ticker.isTicking, isTrue);
expect(state().buildCount, 1);
});
testWidgets('Changing TickerMode does not rebuild widgets with TickerProviderStateMixin', (WidgetTester tester) async {
Widget widgetUnderTest({required bool tickerEnabled}) {
return TickerMode(
enabled: tickerEnabled,
child: const _MultiTickingWidget(),
);
}
_MultiTickingWidgetState state() => tester.state<_MultiTickingWidgetState>(find.byType(_MultiTickingWidget));
await tester.pumpWidget(widgetUnderTest(tickerEnabled: true));
expect(state().ticker.isTicking, isTrue);
expect(state().buildCount, 1);
await tester.pumpWidget(widgetUnderTest(tickerEnabled: false));
expect(state().ticker.isTicking, isFalse);
expect(state().buildCount, 1);
await tester.pumpWidget(widgetUnderTest(tickerEnabled: true));
expect(state().ticker.isTicking, isTrue);
expect(state().buildCount, 1);
});
testWidgets('Moving widgets with SingleTickerProviderStateMixin to a new TickerMode ancestor works', (WidgetTester tester) async {
final GlobalKey tickingWidgetKey = GlobalKey();
Widget widgetUnderTest({required LocalKey tickerModeKey, required bool tickerEnabled}) {
return TickerMode(
key: tickerModeKey,
enabled: tickerEnabled,
child: _TickingWidget(key: tickingWidgetKey),
);
}
// Using different local keys to simulate changing TickerMode ancestors.
await tester.pumpWidget(widgetUnderTest(tickerEnabled: true, tickerModeKey: UniqueKey()));
final State tickerModeState = tester.state(find.byType(TickerMode));
final _TickingWidgetState tickingState = tester.state<_TickingWidgetState>(find.byType(_TickingWidget));
expect(tickingState.ticker.isTicking, isTrue);
await tester.pumpWidget(widgetUnderTest(tickerEnabled: false, tickerModeKey: UniqueKey()));
expect(tester.state(find.byType(TickerMode)), isNot(same(tickerModeState)));
expect(tickingState, same(tester.state<_TickingWidgetState>(find.byType(_TickingWidget))));
expect(tickingState.ticker.isTicking, isFalse);
});
testWidgets('Moving widgets with TickerProviderStateMixin to a new TickerMode ancestor works', (WidgetTester tester) async {
final GlobalKey tickingWidgetKey = GlobalKey();
Widget widgetUnderTest({required LocalKey tickerModeKey, required bool tickerEnabled}) {
return TickerMode(
key: tickerModeKey,
enabled: tickerEnabled,
child: _MultiTickingWidget(key: tickingWidgetKey),
);
}
// Using different local keys to simulate changing TickerMode ancestors.
await tester.pumpWidget(widgetUnderTest(tickerEnabled: true, tickerModeKey: UniqueKey()));
final State tickerModeState = tester.state(find.byType(TickerMode));
final _MultiTickingWidgetState tickingState = tester.state<_MultiTickingWidgetState>(find.byType(_MultiTickingWidget));
expect(tickingState.ticker.isTicking, isTrue);
await tester.pumpWidget(widgetUnderTest(tickerEnabled: false, tickerModeKey: UniqueKey()));
expect(tester.state(find.byType(TickerMode)), isNot(same(tickerModeState)));
expect(tickingState, same(tester.state<_MultiTickingWidgetState>(find.byType(_MultiTickingWidget))));
expect(tickingState.ticker.isTicking, isFalse);
});
testWidgets('Ticking widgets in old route do not rebuild when new route is pushed', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
routes: <String, WidgetBuilder>{
'/foo' : (BuildContext context) => const Text('New route'),
},
home: Row(
children: const <Widget>[
_TickingWidget(),
_MultiTickingWidget(),
Text('Old route'),
],
),
));
_MultiTickingWidgetState multiTickingState() => tester.state<_MultiTickingWidgetState>(find.byType(_MultiTickingWidget, skipOffstage: false));
_TickingWidgetState tickingState() => tester.state<_TickingWidgetState>(find.byType(_TickingWidget, skipOffstage: false));
expect(find.text('Old route'), findsOneWidget);
expect(find.text('New route'), findsNothing);
expect(multiTickingState().ticker.isTicking, isTrue);
expect(multiTickingState().buildCount, 1);
expect(tickingState().ticker.isTicking, isTrue);
expect(tickingState().buildCount, 1);
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/foo');
await tester.pumpAndSettle();
expect(find.text('Old route'), findsNothing);
expect(find.text('New route'), findsOneWidget);
expect(multiTickingState().ticker.isTicking, isFalse);
expect(multiTickingState().buildCount, 1);
expect(tickingState().ticker.isTicking, isFalse);
expect(tickingState().buildCount, 1);
});
}
class _TickingWidget extends StatefulWidget {
const _TickingWidget({Key? key, this.onTick}) : super(key: key);
final VoidCallback? onTick;
@override
State<_TickingWidget> createState() => _TickingWidgetState();
}
class _TickingWidgetState extends State<_TickingWidget> with SingleTickerProviderStateMixin {
late Ticker ticker;
int buildCount = 0;
@override
void initState() {
super.initState();
ticker = createTicker((Duration _) {
widget.onTick?.call();
})..start();
}
@override
Widget build(BuildContext context) {
buildCount += 1;
return Container();
}
@override
void dispose() {
ticker.dispose();
super.dispose();
}
}
class _MultiTickingWidget extends StatefulWidget {
const _MultiTickingWidget({Key? key, this.onTick}) : super(key: key);
final VoidCallback? onTick;
@override
State<_MultiTickingWidget> createState() => _MultiTickingWidgetState();
}
class _MultiTickingWidgetState extends State<_MultiTickingWidget> with TickerProviderStateMixin {
late Ticker ticker;
int buildCount = 0;
@override
void initState() {
super.initState();
ticker = createTicker((Duration _) {
widget.onTick?.call();
})..start();
}
@override
Widget build(BuildContext context) {
buildCount += 1;
return Container();
}
@override
void dispose() {
ticker.dispose();
super.dispose();
}
}