blob: f3a458f5184da6a1ab74af468e2b81126122a0d7 [file] [log] [blame]
// Copyright 2017 The Chromium 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 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'semantics_tester.dart';
void main() {
SemanticsTester semantics;
setUp(() {
debugResetSemanticsIdCounter();
});
testWidgets('scrollable exposes the correct semantic actions', (WidgetTester tester) async {
semantics = new SemanticsTester(tester);
final List<Widget> textWidgets = <Widget>[];
for (int i = 0; i < 80; i++)
textWidgets.add(new Text('$i'));
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new ListView(children: textWidgets),
),
);
expect(semantics,includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollUp]));
await flingUp(tester);
expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown]));
await flingDown(tester, repetitions: 2);
expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollUp]));
await flingUp(tester, repetitions: 5);
expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollDown]));
await flingDown(tester);
expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollUp, SemanticsAction.scrollDown]));
semantics.dispose();
});
testWidgets('showOnScreen works in scrollable', (WidgetTester tester) async {
semantics = new SemanticsTester(tester); // enables semantics tree generation
const double kItemHeight = 40.0;
final List<Widget> containers = <Widget>[];
for (int i = 0; i < 80; i++)
containers.add(new MergeSemantics(child: new Container(
height: kItemHeight,
child: new Text('container $i', textDirection: TextDirection.ltr),
)));
final ScrollController scrollController = new ScrollController(
initialScrollOffset: kItemHeight / 2,
);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new ListView(
controller: scrollController,
children: containers,
),
),
);
expect(scrollController.offset, kItemHeight / 2);
final int firstContainerId = tester.renderObject(find.byWidget(containers.first)).debugSemantics.id;
tester.binding.pipelineOwner.semanticsOwner.performAction(firstContainerId, SemanticsAction.showOnScreen);
await tester.pump();
await tester.pump(const Duration(seconds: 5));
expect(scrollController.offset, 0.0);
semantics.dispose();
});
testWidgets('showOnScreen works with pinned app bar and sliver list', (WidgetTester tester) async {
semantics = new SemanticsTester(tester); // enables semantics tree generation
const double kItemHeight = 100.0;
const double kExpandedAppBarHeight = 56.0;
final List<Widget> containers = <Widget>[];
for (int i = 0; i < 80; i++)
containers.add(new MergeSemantics(child: new Container(
height: kItemHeight,
child: new Text('container $i'),
)));
final ScrollController scrollController = new ScrollController(
initialScrollOffset: kItemHeight / 2,
);
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(),
child: new Scrollable(
controller: scrollController,
viewportBuilder: (BuildContext context, ViewportOffset offset) {
return new Viewport(
offset: offset,
slivers: <Widget>[
const SliverAppBar(
pinned: true,
expandedHeight: kExpandedAppBarHeight,
flexibleSpace: const FlexibleSpaceBar(
title: const Text('App Bar'),
),
),
new SliverList(
delegate: new SliverChildListDelegate(containers),
)
],
);
}),
),
));
expect(scrollController.offset, kItemHeight / 2);
final int firstContainerId = tester.renderObject(find.byWidget(containers.first)).debugSemantics.id;
tester.binding.pipelineOwner.semanticsOwner.performAction(firstContainerId, SemanticsAction.showOnScreen);
await tester.pump();
await tester.pump(const Duration(seconds: 5));
expect(tester.getTopLeft(find.byWidget(containers.first)).dy, kExpandedAppBarHeight);
final int secondContainerId = tester.renderObject(find.byWidget(containers[1])).debugSemantics.id;
tester.binding.pipelineOwner.semanticsOwner.performAction(secondContainerId, SemanticsAction.showOnScreen);
await tester.pump();
await tester.pump(const Duration(seconds: 5));
expect(tester.getTopLeft(find.byWidget(containers[1])).dy, kExpandedAppBarHeight);
semantics.dispose();
});
testWidgets('showOnScreen works with pinned app bar and individual slivers', (WidgetTester tester) async {
semantics = new SemanticsTester(tester); // enables semantics tree generation
const double kItemHeight = 100.0;
const double kExpandedAppBarHeight = 256.0;
final List<Widget> children = <Widget>[];
final List<Widget> slivers = new List<Widget>.generate(30, (int i) {
final Widget child = new MergeSemantics(
child: new Container(
child: new Text('Item $i'),
height: 72.0,
),
);
children.add(child);
return new SliverToBoxAdapter(
child: child,
);
});
final ScrollController scrollController = new ScrollController(
initialScrollOffset: kItemHeight / 2,
);
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new MediaQuery(
data: const MediaQueryData(),
child: new Scrollable(
controller: scrollController,
viewportBuilder: (BuildContext context, ViewportOffset offset) {
return new Viewport(
offset: offset,
slivers: <Widget>[
const SliverAppBar(
pinned: true,
expandedHeight: kExpandedAppBarHeight,
flexibleSpace: const FlexibleSpaceBar(
title: const Text('App Bar'),
),
),
]..addAll(slivers),
);
},
),
),
));
expect(scrollController.offset, kItemHeight / 2);
final int id0 = tester.renderObject(find.byWidget(children[0])).debugSemantics.id;
tester.binding.pipelineOwner.semanticsOwner.performAction(id0, SemanticsAction.showOnScreen);
await tester.pump();
await tester.pump(const Duration(seconds: 5));
expect(tester.getTopLeft(find.byWidget(children[0])).dy, kToolbarHeight);
final int id1 = tester.renderObject(find.byWidget(children[1])).debugSemantics.id;
tester.binding.pipelineOwner.semanticsOwner.performAction(id1, SemanticsAction.showOnScreen);
await tester.pump();
await tester.pump(const Duration(seconds: 5));
expect(tester.getTopLeft(find.byWidget(children[1])).dy, kToolbarHeight);
semantics.dispose();
});
testWidgets('correct scrollProgress', (WidgetTester tester) async {
semantics = new SemanticsTester(tester);
final List<Widget> textWidgets = <Widget>[];
for (int i = 0; i < 80; i++)
textWidgets.add(new Text('$i'));
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new ListView(children: textWidgets),
));
expect(semantics, includesNodeWith(
scrollExtentMin: 0.0,
scrollPosition: 0.0,
scrollExtentMax: 520.0,
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
],
));
await flingUp(tester);
expect(semantics, includesNodeWith(
scrollExtentMin: 0.0,
scrollPosition: 380.2,
scrollExtentMax: 520.0,
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
],
));
await flingUp(tester);
expect(semantics, includesNodeWith(
scrollExtentMin: 0.0,
scrollPosition: 520.0,
scrollExtentMax: 520.0,
actions: <SemanticsAction>[
SemanticsAction.scrollDown,
],
));
semantics.dispose();
});
testWidgets('correct scrollProgress for unbound', (WidgetTester tester) async {
semantics = new SemanticsTester(tester);
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new ListView.builder(
itemExtent: 20.0,
itemBuilder: (BuildContext context, int index) {
return new Text('entry $index');
},
),
));
expect(semantics, includesNodeWith(
scrollExtentMin: 0.0,
scrollPosition: 0.0,
scrollExtentMax: double.infinity,
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
],
));
await flingUp(tester);
expect(semantics, includesNodeWith(
scrollExtentMin: 0.0,
scrollPosition: 380.2,
scrollExtentMax: double.infinity,
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
],
));
await flingUp(tester);
expect(semantics, includesNodeWith(
scrollExtentMin: 0.0,
scrollPosition: 760.4,
scrollExtentMax: double.infinity,
actions: <SemanticsAction>[
SemanticsAction.scrollUp,
SemanticsAction.scrollDown,
],
));
semantics.dispose();
});
testWidgets('Semantics tree is populated mid-scroll', (WidgetTester tester) async {
semantics = new SemanticsTester(tester);
final List<Widget> children = <Widget>[];
for (int i = 0; i < 80; i++)
children.add(new Container(
child: new Text('Item $i'),
height: 40.0,
));
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new ListView(children: children),
),
);
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
await gesture.moveBy(const Offset(0.0, -40.0));
await tester.pump();
expect(semantics, includesNodeWith(label: 'Item 1'));
expect(semantics, includesNodeWith(label: 'Item 2'));
expect(semantics, includesNodeWith(label: 'Item 3'));
semantics.dispose();
});
testWidgets('Can toggle semantics on, off, on without crash', (WidgetTester tester) async {
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new ListView(
children: new List<Widget>.generate(40, (int i) {
return new Container(
child: new Text('item $i'),
height: 400.0,
);
}),
),
),
);
final TestSemantics expectedSemantics = new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
children: <TestSemantics>[
new TestSemantics(
actions: <SemanticsAction>[SemanticsAction.scrollUp],
children: <TestSemantics>[
new TestSemantics(
label: r'item 0',
textDirection: TextDirection.ltr,
),
new TestSemantics(
label: r'item 1',
textDirection: TextDirection.ltr,
),
new TestSemantics(
flags: <SemanticsFlag>[
SemanticsFlag.isHidden,
],
label: r'item 2',
),
],
),
],
),
],
);
// Start with semantics off.
expect(tester.binding.pipelineOwner.semanticsOwner, isNull);
// Semantics on
semantics = new SemanticsTester(tester);
await tester.pumpAndSettle();
expect(tester.binding.pipelineOwner.semanticsOwner, isNotNull);
expect(semantics, hasSemantics(expectedSemantics, ignoreId: true, ignoreRect: true, ignoreTransform: true));
// Semantics off
semantics.dispose();
await tester.pumpAndSettle();
expect(tester.binding.pipelineOwner.semanticsOwner, isNull);
// Semantics on
semantics = new SemanticsTester(tester);
await tester.pumpAndSettle();
expect(tester.binding.pipelineOwner.semanticsOwner, isNotNull);
expect(semantics, hasSemantics(expectedSemantics, ignoreId: true, ignoreRect: true, ignoreTransform: true));
semantics.dispose();
});
}
Future<Null> flingUp(WidgetTester tester, { int repetitions: 1 }) => fling(tester, const Offset(0.0, -200.0), repetitions);
Future<Null> flingDown(WidgetTester tester, { int repetitions: 1 }) => fling(tester, const Offset(0.0, 200.0), repetitions);
Future<Null> flingRight(WidgetTester tester, { int repetitions: 1 }) => fling(tester, const Offset(200.0, 0.0), repetitions);
Future<Null> flingLeft(WidgetTester tester, { int repetitions: 1 }) => fling(tester, const Offset(-200.0, 0.0), repetitions);
Future<Null> fling(WidgetTester tester, Offset offset, int repetitions) async {
while (repetitions-- > 0) {
await tester.fling(find.byType(ListView), offset, 1000.0);
await tester.pump();
await tester.pump(const Duration(seconds: 5));
}
}