mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
* Send scroll progress with ScrollCompletedSemanticsEvent This requires engine change https://github.com/flutter/engine/pull/4144 * fix analyze warning * review comment * Roll engine to 45b11f742d38ebf564a5a832b1af00661d1a31fa * fix test
333 lines
12 KiB
Dart
333 lines
12 KiB
Dart
// 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 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
|
|
import 'semantics_tester.dart';
|
|
|
|
void main() {
|
|
testWidgets('scrollable exposes the correct semantic actions', (WidgetTester tester) async {
|
|
final SemanticsTester 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]));
|
|
});
|
|
|
|
testWidgets('showOnScreen works in scrollable', (WidgetTester tester) async {
|
|
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);
|
|
});
|
|
|
|
testWidgets('showOnScreen works with pinned app bar and sliver list', (WidgetTester tester) async {
|
|
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);
|
|
});
|
|
|
|
testWidgets('showOnScreen works with pinned app bar and individual slivers', (WidgetTester tester) async {
|
|
new SemanticsTester(tester); // enables semantics tree generation
|
|
|
|
const double kItemHeight = 100.0;
|
|
const double kExpandedAppBarHeight = 256.0;
|
|
|
|
|
|
final List<Widget> semantics = <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,
|
|
),
|
|
);
|
|
semantics.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(semantics[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(semantics[0])).dy, kToolbarHeight);
|
|
|
|
final int id1 = tester.renderObject(find.byWidget(semantics[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(semantics[1])).dy, kToolbarHeight);
|
|
});
|
|
|
|
testWidgets('vertical scrolling sends ScrollCompletedSemanticsEvent', (WidgetTester tester) async {
|
|
final List<dynamic> messages = <dynamic>[];
|
|
SystemChannels.accessibility.setMockMessageHandler((dynamic message) {
|
|
messages.add(message);
|
|
});
|
|
|
|
final SemanticsTester 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),
|
|
));
|
|
|
|
await flingUp(tester);
|
|
|
|
expect(messages, isNot(hasLength(0)));
|
|
expect(messages.every((dynamic message) => message['type'] == 'scroll'), isTrue);
|
|
|
|
Map<String, Object> message = messages.last['data'];
|
|
expect(message['axis'], 'v');
|
|
expect(message['pixels'], isPositive);
|
|
expect(message['minScrollExtent'], 0.0);
|
|
expect(message['maxScrollExtent'], 520.0);
|
|
|
|
messages.clear();
|
|
await flingDown(tester);
|
|
|
|
expect(messages, isNot(hasLength(0)));
|
|
expect(messages.every((dynamic message) => message['type'] == 'scroll'), isTrue);
|
|
|
|
message = messages.last['data'];
|
|
expect(message['axis'], 'v');
|
|
expect(message['pixels'], isNonNegative);
|
|
expect(message['minScrollExtent'], 0.0);
|
|
expect(message['maxScrollExtent'], 520.0);
|
|
|
|
semantics.dispose();
|
|
});
|
|
|
|
testWidgets('horizontal scrolling sends ScrollCompletedSemanticsEvent', (WidgetTester tester) async {
|
|
final List<dynamic> messages = <dynamic>[];
|
|
SystemChannels.accessibility.setMockMessageHandler((dynamic message) {
|
|
messages.add(message);
|
|
});
|
|
|
|
final SemanticsTester semantics = new SemanticsTester(tester);
|
|
|
|
final List<Widget> children = <Widget>[];
|
|
for (int i = 0; i < 80; i++)
|
|
children.add(new Container(
|
|
child: new Text('$i'),
|
|
width: 100.0,
|
|
));
|
|
await tester.pumpWidget(new Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: new ListView(
|
|
children: children,
|
|
scrollDirection: Axis.horizontal,
|
|
),
|
|
));
|
|
|
|
await flingLeft(tester);
|
|
|
|
expect(messages, isNot(hasLength(0)));
|
|
expect(messages.every((dynamic message) => message['type'] == 'scroll'), isTrue);
|
|
|
|
Map<String, Object> message = messages.last['data'];
|
|
expect(message['axis'], 'h');
|
|
expect(message['pixels'], isPositive);
|
|
expect(message['minScrollExtent'], 0.0);
|
|
expect(message['maxScrollExtent'], 7200.0);
|
|
|
|
messages.clear();
|
|
await flingRight(tester);
|
|
|
|
expect(messages, isNot(hasLength(0)));
|
|
expect(messages.every((dynamic message) => message['type'] == 'scroll'), isTrue);
|
|
|
|
message = messages.last['data'];
|
|
expect(message['axis'], 'h');
|
|
expect(message['pixels'], isNonNegative);
|
|
expect(message['minScrollExtent'], 0.0);
|
|
expect(message['maxScrollExtent'], 7200.0);
|
|
|
|
semantics.dispose();
|
|
});
|
|
|
|
testWidgets('Semantics tree is populated mid-scroll', (WidgetTester tester) async {
|
|
final SemanticsTester 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();
|
|
});
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|