mirror of
https://github.com/alibaba/flutter-go.git
synced 2025-07-15 03:04:25 +08:00
Add:创建 flutter go web 版
This commit is contained in:
375
packages/flutter_web/test/widgets/semantics_traversal_test.dart
Normal file
375
packages/flutter_web/test/widgets/semantics_traversal_test.dart
Normal file
@ -0,0 +1,375 @@
|
||||
// Copyright 2018 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:collection';
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter_web_ui/ui.dart';
|
||||
|
||||
import 'package:flutter_web/material.dart';
|
||||
import 'package:flutter_web/rendering.dart';
|
||||
import 'package:flutter_web/semantics.dart';
|
||||
import 'package:flutter_web/widgets.dart';
|
||||
import 'package:flutter_web_test/flutter_web_test.dart';
|
||||
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
typedef TraversalTestFunction = Future<void> Function(TraversalTester tester);
|
||||
const Size tenByTen = Size(10.0, 10.0);
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
debugResetSemanticsIdCounter();
|
||||
});
|
||||
|
||||
void testTraversal(String description, TraversalTestFunction testFunction) {
|
||||
testWidgets(description, (WidgetTester tester) async {
|
||||
final TraversalTester traversalTester = TraversalTester(tester);
|
||||
await testFunction(traversalTester);
|
||||
traversalTester.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
// ┌───┐ ┌───┐
|
||||
// │ A │>│ B │
|
||||
// └───┘ └───┘
|
||||
testTraversal('Semantics traverses horizontally left-to-right',
|
||||
(TraversalTester tester) async {
|
||||
await tester.test(
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <String, Rect>{
|
||||
'A': const Offset(0.0, 0.0) & tenByTen,
|
||||
'B': const Offset(20.0, 0.0) & tenByTen,
|
||||
},
|
||||
expectedTraversal: 'A B',
|
||||
);
|
||||
});
|
||||
|
||||
// ┌───┐ ┌───┐
|
||||
// │ A │<│ B │
|
||||
// └───┘ └───┘
|
||||
testTraversal('Semantics traverses horizontally right-to-left',
|
||||
(TraversalTester tester) async {
|
||||
await tester.test(
|
||||
textDirection: TextDirection.rtl,
|
||||
children: <String, Rect>{
|
||||
'A': const Offset(0.0, 0.0) & tenByTen,
|
||||
'B': const Offset(20.0, 0.0) & tenByTen,
|
||||
},
|
||||
expectedTraversal: 'B A',
|
||||
);
|
||||
});
|
||||
|
||||
// ┌───┐
|
||||
// │ A │
|
||||
// └───┘
|
||||
// V
|
||||
// ┌───┐
|
||||
// │ B │
|
||||
// └───┘
|
||||
testTraversal('Semantics traverses vertically top-to-bottom',
|
||||
(TraversalTester tester) async {
|
||||
for (TextDirection textDirection in TextDirection.values) {
|
||||
await tester.test(
|
||||
textDirection: textDirection,
|
||||
children: <String, Rect>{
|
||||
'A': const Offset(0.0, 0.0) & tenByTen,
|
||||
'B': const Offset(0.0, 20.0) & tenByTen,
|
||||
},
|
||||
expectedTraversal: 'A B',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// ┌───┐ ┌───┐
|
||||
// │ A │>│ B │
|
||||
// └───┘ └───┘
|
||||
// ┌─────┘
|
||||
// V
|
||||
// ┌───┐ ┌───┐
|
||||
// │ C │>│ D │
|
||||
// └───┘ └───┘
|
||||
testTraversal('Semantics traverses a grid left-to-right',
|
||||
(TraversalTester tester) async {
|
||||
await tester.test(
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <String, Rect>{
|
||||
'A': const Offset(0.0, 0.0) & tenByTen,
|
||||
'B': const Offset(20.0, 0.0) & tenByTen,
|
||||
'C': const Offset(0.0, 20.0) & tenByTen,
|
||||
'D': const Offset(20.0, 20.0) & tenByTen,
|
||||
},
|
||||
expectedTraversal: 'A B C D',
|
||||
);
|
||||
});
|
||||
|
||||
// ┌───┐ ┌───┐
|
||||
// │ A │<│ B │
|
||||
// └───┘ └───┘
|
||||
// └─────┐
|
||||
// V
|
||||
// ┌───┐ ┌───┐
|
||||
// │ C │<│ D │
|
||||
// └───┘ └───┘
|
||||
testTraversal('Semantics traverses a grid right-to-left',
|
||||
(TraversalTester tester) async {
|
||||
await tester.test(
|
||||
textDirection: TextDirection.rtl,
|
||||
children: <String, Rect>{
|
||||
'A': const Offset(0.0, 0.0) & tenByTen,
|
||||
'B': const Offset(20.0, 0.0) & tenByTen,
|
||||
'C': const Offset(0.0, 20.0) & tenByTen,
|
||||
'D': const Offset(20.0, 20.0) & tenByTen,
|
||||
},
|
||||
expectedTraversal: 'B A D C',
|
||||
);
|
||||
});
|
||||
|
||||
// ┌───┐ ┌───┐
|
||||
// │ A │ │ C │
|
||||
// └───┘<->┌───┐<->└───┘
|
||||
// │ B │
|
||||
// └───┘
|
||||
testTraversal('Semantics traverses vertically overlapping nodes horizontally',
|
||||
(TraversalTester tester) async {
|
||||
final Map<String, Rect> children = <String, Rect>{
|
||||
'A': const Offset(0.0, 0.0) & tenByTen,
|
||||
'B': const Offset(20.0, 5.0) & tenByTen,
|
||||
'C': const Offset(40.0, 0.0) & tenByTen,
|
||||
};
|
||||
|
||||
await tester.test(
|
||||
textDirection: TextDirection.ltr,
|
||||
children: children,
|
||||
expectedTraversal: 'A B C',
|
||||
);
|
||||
|
||||
await tester.test(
|
||||
textDirection: TextDirection.rtl,
|
||||
children: children,
|
||||
expectedTraversal: 'C B A',
|
||||
);
|
||||
});
|
||||
|
||||
// LTR:
|
||||
// ┌───┐ ┌───┐ ┌───┐ ┌───┐
|
||||
// │ A │>│ B │>│ C │>│ D │
|
||||
// └───┘ └───┘ └───┘ └───┘
|
||||
// ┌─────────────────┘
|
||||
// V
|
||||
// ┌───┐ ┌─────────┐ ┌───┐
|
||||
// │ E │ │ │>│ H │
|
||||
// └───┘ │ G │ └───┘
|
||||
// V │ │ V
|
||||
// ┌───┐ │ │ ┌───┐
|
||||
// │ F │>│ │ │ I │
|
||||
// └───┘ └─────────┘ └───┘
|
||||
// ┌─────────────────┘
|
||||
// V
|
||||
// ┌───┐ ┌───┐ ┌───┐ ┌───┐
|
||||
// │ J │>│ K │>│ L │>│ M │
|
||||
// └───┘ └───┘ └───┘ └───┘
|
||||
//
|
||||
// RTL:
|
||||
// ┌───┐ ┌───┐ ┌───┐ ┌───┐
|
||||
// │ A │<│ B │<│ C │<│ D │
|
||||
// └───┘ └───┘ └───┘ └───┘
|
||||
// └─────────────────┐
|
||||
// V
|
||||
// ┌───┐ ┌─────────┐ ┌───┐
|
||||
// │ E │<│ │ │ H │
|
||||
// └───┘ │ G │ └───┘
|
||||
// V │ │ V
|
||||
// ┌───┐ │ │ ┌───┐
|
||||
// │ F │ │ │<│ I │
|
||||
// └───┘ └─────────┘ └───┘
|
||||
// └─────────────────┐
|
||||
// V
|
||||
// ┌───┐ ┌───┐ ┌───┐ ┌───┐
|
||||
// │ J │<│ K │<│ L │<│ M │
|
||||
// └───┘ └───┘ └───┘ └───┘
|
||||
testTraversal(
|
||||
'Semantics traverses vertical groups, then horizontal groups, then knots',
|
||||
(TraversalTester tester) async {
|
||||
final Map<String, Rect> children = <String, Rect>{
|
||||
'A': const Offset(0.0, 0.0) & tenByTen,
|
||||
'B': const Offset(20.0, 0.0) & tenByTen,
|
||||
'C': const Offset(40.0, 0.0) & tenByTen,
|
||||
'D': const Offset(60.0, 0.0) & tenByTen,
|
||||
'E': const Offset(0.0, 20.0) & tenByTen,
|
||||
'F': const Offset(0.0, 40.0) & tenByTen,
|
||||
'G': const Offset(20.0, 20.0) & (tenByTen * 2.0),
|
||||
'H': const Offset(60.0, 20.0) & tenByTen,
|
||||
'I': const Offset(60.0, 40.0) & tenByTen,
|
||||
'J': const Offset(0.0, 60.0) & tenByTen,
|
||||
'K': const Offset(20.0, 60.0) & tenByTen,
|
||||
'L': const Offset(40.0, 60.0) & tenByTen,
|
||||
'M': const Offset(60.0, 60.0) & tenByTen,
|
||||
};
|
||||
|
||||
await tester.test(
|
||||
textDirection: TextDirection.ltr,
|
||||
children: children,
|
||||
expectedTraversal: 'A B C D E F G H I J K L M',
|
||||
);
|
||||
|
||||
await tester.test(
|
||||
textDirection: TextDirection.rtl,
|
||||
children: children,
|
||||
expectedTraversal: 'D C B A H I G E F M L K J',
|
||||
);
|
||||
});
|
||||
|
||||
// The following test tests traversal of the simplest "knot", which is two
|
||||
// nodes overlapping both vertically and horizontally. For example:
|
||||
//
|
||||
// ┌─────────┐
|
||||
// │ │
|
||||
// │ A │
|
||||
// │ ┌───┼─────┐
|
||||
// │ │ │ │
|
||||
// └─────┼───┘ │
|
||||
// │ B │
|
||||
// │ │
|
||||
// └─────────┘
|
||||
//
|
||||
// The outcome depends on the relative positions of the centers of `Rect`s of
|
||||
// their respective boxes, specifically the direction (i.e. angle) of the
|
||||
// vector pointing from A to B. We test different angles, one for each octant:
|
||||
//
|
||||
// -3π/4 -π/2 -π/4
|
||||
// ╲ │ ╱
|
||||
// ╲ 1│2 ╱
|
||||
// ╲ │ ╱
|
||||
// i=0 ╲│╱ 3
|
||||
// π ──────┼────── 0
|
||||
// 7 ╱│╲ 4
|
||||
// ╱ │ ╲
|
||||
// ╱ 6│5 ╲
|
||||
// ╱ │ ╲
|
||||
// 3π/4 π/2 π/4
|
||||
//
|
||||
// For LTR, angles falling into octants 3, 4, 5, and 6, produce A -> B, all
|
||||
// others produce B -> A.
|
||||
//
|
||||
// For RTL, angles falling into octants 5, 6, 7, and 0, produce A -> B, all
|
||||
// others produce B -> A.
|
||||
testTraversal('Semantics sorts knots', (TraversalTester tester) async {
|
||||
const double start = -math.pi + math.pi / 8.0;
|
||||
|
||||
for (int i = 0; i < 8; i += 1) {
|
||||
final double angle = start + i.toDouble() * math.pi / 4.0;
|
||||
// These values should be truncated so that double precision rounding
|
||||
// issues won't impact the heights/widths and throw off the traversal
|
||||
// ordering.
|
||||
final double dx = (math.cos(angle) * 15.0) / 10.0;
|
||||
final double dy = (math.sin(angle) * 15.0) / 10.0;
|
||||
|
||||
final Map<String, Rect> children = <String, Rect>{
|
||||
'A': const Offset(10.0, 10.0) & tenByTen,
|
||||
'B': Offset(10.0 + dx, 10.0 + dy) & tenByTen,
|
||||
};
|
||||
|
||||
try {
|
||||
await tester.test(
|
||||
textDirection: TextDirection.ltr,
|
||||
children: children,
|
||||
expectedTraversal: 3 <= i && i <= 6 ? 'A B' : 'B A',
|
||||
);
|
||||
|
||||
await tester.test(
|
||||
textDirection: TextDirection.rtl,
|
||||
children: children,
|
||||
expectedTraversal: 1 <= i && i <= 4 ? 'B A' : 'A B',
|
||||
);
|
||||
} catch (error) {
|
||||
fail('Test failed with i == $i, angle == ${angle / math.pi}π\n'
|
||||
'$error');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class TraversalTester {
|
||||
TraversalTester(this.tester) : semantics = SemanticsTester(tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
final SemanticsTester semantics;
|
||||
|
||||
Future<void> test({
|
||||
TextDirection textDirection,
|
||||
Map<String, Rect> children,
|
||||
String expectedTraversal,
|
||||
}) async {
|
||||
assert(children is LinkedHashMap);
|
||||
await tester.pumpWidget(Container(
|
||||
child: Directionality(
|
||||
textDirection: textDirection,
|
||||
child: Semantics(
|
||||
textDirection: textDirection,
|
||||
child: CustomMultiChildLayout(
|
||||
delegate: TestLayoutDelegate(children),
|
||||
children: children.keys.map<Widget>((String label) {
|
||||
return LayoutId(
|
||||
id: label,
|
||||
child: Semantics(
|
||||
container: true,
|
||||
explicitChildNodes: true,
|
||||
label: label,
|
||||
child: SizedBox(
|
||||
width: children[label].width,
|
||||
height: children[label].height,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
)));
|
||||
|
||||
expect(
|
||||
semantics,
|
||||
hasSemantics(
|
||||
TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
TestSemantics.rootChild(
|
||||
textDirection: textDirection,
|
||||
children: expectedTraversal
|
||||
.split(' ')
|
||||
.map<TestSemantics>((String label) {
|
||||
return TestSemantics(
|
||||
label: label,
|
||||
);
|
||||
}).toList(),
|
||||
)
|
||||
],
|
||||
),
|
||||
ignoreTransform: true,
|
||||
ignoreRect: true,
|
||||
ignoreId: true,
|
||||
childOrder: DebugSemanticsDumpOrder.traversalOrder,
|
||||
));
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
semantics.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class TestLayoutDelegate extends MultiChildLayoutDelegate {
|
||||
TestLayoutDelegate(this.children);
|
||||
|
||||
final Map<String, Rect> children;
|
||||
|
||||
@override
|
||||
void performLayout(Size size) {
|
||||
children.forEach((String label, Rect rect) {
|
||||
layoutChild(label, BoxConstraints.loose(size));
|
||||
positionChild(label, rect.topLeft);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) =>
|
||||
oldDelegate == this;
|
||||
}
|
Reference in New Issue
Block a user