blob: 7a84193032dbb67868468f9852ec48c991f794cc [file] [log] [blame]
// Copyright 2013 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:adaptive_scaffold/src/adaptive_layout.dart';
import 'package:adaptive_scaffold/src/breakpoints.dart';
import 'package:adaptive_scaffold/src/slot_layout.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
class TestBreakpoint0 extends Breakpoint {
@override
bool isActive(BuildContext context) {
return MediaQuery.of(context).size.width >= 0;
}
}
class TestBreakpoint400 extends Breakpoint {
@override
bool isActive(BuildContext context) {
return MediaQuery.of(context).size.width > 400;
}
}
class TestBreakpoint800 extends Breakpoint {
@override
bool isActive(BuildContext context) {
return MediaQuery.of(context).size.width > 800;
}
}
final Finder tnav = find.byKey(const Key('tnav'));
final Finder snav = find.byKey(const Key('snav'));
final Finder pnav = find.byKey(const Key('pnav'));
final Finder bnav = find.byKey(const Key('bnav'));
final Finder b = find.byKey(const Key('b'));
final Finder sb = find.byKey(const Key('sb'));
Widget on(BuildContext _) {
return const SizedBox(width: 10, height: 10);
}
Future<MediaQuery> layout({
required double width,
required WidgetTester tester,
Axis orientation = Axis.horizontal,
TextDirection directionality = TextDirection.ltr,
double? bodyRatio,
bool animations = true,
}) async {
await tester.binding.setSurfaceSize(Size(width, 800));
return MediaQuery(
data: MediaQueryData(size: Size(width, 800)),
child: Directionality(
textDirection: directionality,
child: AdaptiveLayout(
bodyOrientation: orientation,
bodyRatio: bodyRatio,
internalAnimations: animations,
primaryNavigation: SlotLayout(
config: <Breakpoint, SlotLayoutConfig>{
TestBreakpoint0():
SlotLayout.from(key: const Key('pnav'), builder: on),
TestBreakpoint400():
SlotLayout.from(key: const Key('pnav1'), builder: on),
TestBreakpoint800():
SlotLayout.from(key: const Key('pnav2'), builder: on),
},
),
secondaryNavigation: SlotLayout(
config: <Breakpoint, SlotLayoutConfig>{
TestBreakpoint0():
SlotLayout.from(key: const Key('snav'), builder: on),
TestBreakpoint400():
SlotLayout.from(key: const Key('snav1'), builder: on),
TestBreakpoint800():
SlotLayout.from(key: const Key('snav2'), builder: on),
},
),
topNavigation: SlotLayout(
config: <Breakpoint, SlotLayoutConfig>{
TestBreakpoint0():
SlotLayout.from(key: const Key('tnav'), builder: on),
TestBreakpoint400():
SlotLayout.from(key: const Key('tnav1'), builder: on),
TestBreakpoint800():
SlotLayout.from(key: const Key('tnav2'), builder: on),
},
),
bottomNavigation: SlotLayout(
config: <Breakpoint, SlotLayoutConfig>{
TestBreakpoint0():
SlotLayout.from(key: const Key('bnav'), builder: on),
TestBreakpoint400():
SlotLayout.from(key: const Key('bnav1'), builder: on),
TestBreakpoint800():
SlotLayout.from(key: const Key('bnav2'), builder: on),
},
),
body: SlotLayout(
config: <Breakpoint, SlotLayoutConfig>{
TestBreakpoint0(): SlotLayout.from(
key: const Key('b'),
builder: (_) => Container(color: Colors.red),
),
TestBreakpoint400(): SlotLayout.from(
key: const Key('b1'),
builder: (_) => Container(color: Colors.red),
),
TestBreakpoint800(): SlotLayout.from(
key: const Key('b2'),
builder: (_) => Container(color: Colors.red),
),
},
),
secondaryBody: SlotLayout(
config: <Breakpoint, SlotLayoutConfig>{
TestBreakpoint0(): SlotLayout.from(
key: const Key('sb'),
builder: (_) => Container(color: Colors.blue),
),
TestBreakpoint400(): SlotLayout.from(
key: const Key('sb1'),
builder: (_) => Container(color: Colors.blue),
),
TestBreakpoint800(): SlotLayout.from(
key: const Key('sb2'),
builder: (_) => Container(color: Colors.blue),
),
},
),
),
),
);
}
AnimatedWidget leftOutIn(Widget child, Animation<double> animation) {
return SlideTransition(
key: Key('in-${child.key}'),
position: Tween<Offset>(
begin: const Offset(-1, 0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
AnimatedWidget leftInOut(Widget child, Animation<double> animation) {
return SlideTransition(
key: Key('out-${child.key}'),
position: Tween<Offset>(
begin: Offset.zero,
end: const Offset(-1, 0),
).animate(animation),
child: child,
);
}
MediaQuery slot(double width) {
return MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window)
.copyWith(size: Size(width, 800)),
child: Directionality(
textDirection: TextDirection.ltr,
child: SlotLayout(
config: <Breakpoint, SlotLayoutConfig>{
TestBreakpoint0(): SlotLayout.from(
inAnimation: leftOutIn,
outAnimation: leftInOut,
key: const Key('0'),
builder: (_) => const SizedBox(width: 10, height: 10),
),
TestBreakpoint400(): SlotLayout.from(
inAnimation: leftOutIn,
outAnimation: leftInOut,
key: const Key('400'),
builder: (_) => const SizedBox(width: 10, height: 10),
),
},
),
),
);
}
void main() {
testWidgets(
'slot layout dislays correct item of config based on screen width',
(WidgetTester tester) async {
MediaQuery slot(double width) {
return MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window)
.copyWith(size: Size(width, 800)),
child: Directionality(
textDirection: TextDirection.ltr,
child: SlotLayout(
config: <Breakpoint, SlotLayoutConfig>{
TestBreakpoint0(): SlotLayout.from(
key: const Key('0'), builder: (_) => const SizedBox()),
TestBreakpoint400(): SlotLayout.from(
key: const Key('400'), builder: (_) => const SizedBox()),
TestBreakpoint800(): SlotLayout.from(
key: const Key('800'), builder: (_) => const SizedBox()),
},
),
),
);
}
await tester.pumpWidget(slot(300));
expect(find.byKey(const Key('0')), findsOneWidget);
expect(find.byKey(const Key('400')), findsNothing);
expect(find.byKey(const Key('800')), findsNothing);
await tester.pumpWidget(slot(500));
expect(find.byKey(const Key('0')), findsNothing);
expect(find.byKey(const Key('400')), findsOneWidget);
expect(find.byKey(const Key('800')), findsNothing);
await tester.pumpWidget(slot(1000));
expect(find.byKey(const Key('0')), findsNothing);
expect(find.byKey(const Key('400')), findsNothing);
expect(find.byKey(const Key('800')), findsOneWidget);
});
testWidgets('adaptive layout displays children in correct places',
(WidgetTester tester) async {
await tester.pumpWidget(await layout(width: 400, tester: tester));
await tester.pumpAndSettle();
expect(tester.getTopLeft(tnav), Offset.zero);
expect(tester.getTopLeft(snav), const Offset(390, 10));
expect(tester.getTopLeft(pnav), const Offset(0, 10));
expect(tester.getTopLeft(bnav), const Offset(0, 790));
expect(tester.getTopLeft(b), const Offset(10, 10));
expect(tester.getBottomRight(b), const Offset(200, 790));
expect(tester.getTopLeft(sb), const Offset(200, 10));
expect(tester.getBottomRight(sb), const Offset(390, 790));
});
testWidgets('adaptive layout correct layout when body vertical',
(WidgetTester tester) async {
await tester.pumpWidget(
await layout(width: 400, tester: tester, orientation: Axis.vertical));
await tester.pumpAndSettle();
expect(tester.getTopLeft(tnav), Offset.zero);
expect(tester.getTopLeft(snav), const Offset(390, 10));
expect(tester.getTopLeft(pnav), const Offset(0, 10));
expect(tester.getTopLeft(bnav), const Offset(0, 790));
expect(tester.getTopLeft(b), const Offset(10, 10));
expect(tester.getBottomRight(b), const Offset(390, 400));
expect(tester.getTopLeft(sb), const Offset(10, 400));
expect(tester.getBottomRight(sb), const Offset(390, 790));
});
testWidgets('adaptive layout correct layout when rtl',
(WidgetTester tester) async {
await tester.pumpWidget(await layout(
width: 400, tester: tester, directionality: TextDirection.rtl));
await tester.pumpAndSettle();
expect(tester.getTopLeft(tnav), Offset.zero);
expect(tester.getTopLeft(snav), const Offset(0, 10));
expect(tester.getTopLeft(pnav), const Offset(390, 10));
expect(tester.getTopLeft(bnav), const Offset(0, 790));
expect(tester.getTopLeft(b), const Offset(200, 10));
expect(tester.getBottomRight(b), const Offset(390, 790));
expect(tester.getTopLeft(sb), const Offset(10, 10));
expect(tester.getBottomRight(sb), const Offset(200, 790));
});
testWidgets('adaptive layout correct layout when body ratio not default',
(WidgetTester tester) async {
await tester
.pumpWidget(await layout(width: 400, tester: tester, bodyRatio: 1 / 3));
await tester.pumpAndSettle();
expect(tester.getTopLeft(tnav), Offset.zero);
expect(tester.getTopLeft(snav), const Offset(390, 10));
expect(tester.getTopLeft(pnav), const Offset(0, 10));
expect(tester.getTopLeft(bnav), const Offset(0, 790));
expect(tester.getTopLeft(b), const Offset(10, 10));
expect(tester.getBottomRight(b),
offsetMoreOrLessEquals(const Offset(136.7, 790), epsilon: 1.0));
expect(tester.getTopLeft(sb),
offsetMoreOrLessEquals(const Offset(136.7, 10), epsilon: 1.0));
expect(tester.getBottomRight(sb), const Offset(390, 790));
});
final Finder begin = find.byKey(const Key('0'));
final Finder end = find.byKey(const Key('400'));
Finder slideIn(String key) => find.byKey(Key('in-${Key(key)}'));
Finder slideOut(String key) => find.byKey(Key('out-${Key(key)}'));
testWidgets(
'slot layout properly switches between items with the appropriate animation',
(WidgetTester tester) async {
await tester.pumpWidget(slot(300));
expect(begin, findsOneWidget);
expect(end, findsNothing);
await tester.pumpWidget(slot(500));
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
expect(tester.widget<SlideTransition>(slideOut('0')).position.value,
const Offset(-0.5, 0));
expect(tester.widget<SlideTransition>(slideIn('400')).position.value,
const Offset(-0.5, 0));
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
expect(tester.widget<SlideTransition>(slideOut('0')).position.value,
const Offset(-1.0, 0));
expect(tester.widget<SlideTransition>(slideIn('400')).position.value,
Offset.zero);
await tester.pumpAndSettle();
expect(begin, findsNothing);
expect(end, findsOneWidget);
});
testWidgets('slot layout can tolerate rapid changes in breakpoints',
(WidgetTester tester) async {
await tester.pumpWidget(slot(300));
expect(begin, findsOneWidget);
expect(end, findsNothing);
await tester.pumpWidget(slot(500));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(tester.widget<SlideTransition>(slideOut('0')).position.value,
offsetMoreOrLessEquals(const Offset(-0.1, 0), epsilon: 0.05));
expect(tester.widget<SlideTransition>(slideIn('400')).position.value,
offsetMoreOrLessEquals(const Offset(-0.9, 0), epsilon: 0.05));
await tester.pumpWidget(slot(300));
await tester.pumpAndSettle();
expect(begin, findsOneWidget);
expect(end, findsNothing);
}, skip: true);
// This test reflects the behavior of the internal animations of both the body
// and secondary body and also the navigational items. This is reflected in
// the changes in LTRB offsets from all sides instead of just LR for the body
// animations.
testWidgets('adaptive layout handles internal animations correctly',
(WidgetTester tester) async {
final Finder b = find.byKey(const Key('b'));
final Finder sb = find.byKey(const Key('sb'));
await tester.pumpWidget(await layout(width: 400, tester: tester));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(tester.getTopLeft(b), const Offset(1, 1));
expect(tester.getBottomRight(b),
offsetMoreOrLessEquals(const Offset(395.8, 799), epsilon: 1.0));
expect(tester.getTopLeft(sb),
offsetMoreOrLessEquals(const Offset(395.8, 1.0), epsilon: 1.0));
expect(tester.getBottomRight(sb),
offsetMoreOrLessEquals(const Offset(594.8, 799.0), epsilon: 1.0));
await tester.pump();
await tester.pump(const Duration(milliseconds: 400));
expect(tester.getTopLeft(b), const Offset(5, 5));
expect(tester.getBottomRight(b),
offsetMoreOrLessEquals(const Offset(294.2, 795), epsilon: 1.0));
expect(tester.getTopLeft(sb),
offsetMoreOrLessEquals(const Offset(294.2, 5.0), epsilon: 1.0));
expect(tester.getBottomRight(sb),
offsetMoreOrLessEquals(const Offset(489.2, 795.0), epsilon: 1.0));
await tester.pump();
await tester.pump(const Duration(milliseconds: 400));
expect(tester.getTopLeft(b), const Offset(9, 9));
expect(tester.getBottomRight(b),
offsetMoreOrLessEquals(const Offset(201.7, 791), epsilon: 1.0));
expect(tester.getTopLeft(sb),
offsetMoreOrLessEquals(const Offset(201.7, 9.0), epsilon: 1.0));
expect(tester.getBottomRight(sb),
offsetMoreOrLessEquals(const Offset(392.7, 791), epsilon: 1.0));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(tester.getTopLeft(b), const Offset(10, 10));
expect(tester.getBottomRight(b), const Offset(200, 790));
expect(tester.getTopLeft(sb), const Offset(200, 10));
expect(tester.getBottomRight(sb), const Offset(390, 790));
}, skip: true);
testWidgets('adaptive layout does not animate when animations off',
(WidgetTester tester) async {
final Finder b = find.byKey(const Key('b'));
final Finder sb = find.byKey(const Key('sb'));
await tester.pumpWidget(
await layout(width: 400, tester: tester, animations: false));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(tester.getTopLeft(b), const Offset(10, 10));
expect(tester.getBottomRight(b), const Offset(200, 790));
expect(tester.getTopLeft(sb), const Offset(200, 10));
expect(tester.getBottomRight(sb), const Offset(390, 790));
}, skip: true);
}
// Some animation related tests are temporarily disabled while waiting for the
// roll to packages to be fixed.
// TODO(serenabehera): remove the skip: true from the tests once they properly
// pass.