@@ -12,6 +12,10 @@ import 'package:flutter_test/flutter_test.dart';
12
12
13
13
import 'rendering_tester.dart' ;
14
14
15
+ double _caretMarginOf (RenderEditable renderEditable) {
16
+ return renderEditable.cursorWidth + 1.0 ;
17
+ }
18
+
15
19
void _applyParentData (List <RenderBox > inlineRenderBoxes, InlineSpan span) {
16
20
int index = 0 ;
17
21
RenderBox ? previousBox;
@@ -1184,8 +1188,107 @@ void main() {
1184
1188
});
1185
1189
1186
1190
group ('hit testing' , () {
1191
+ final TextSelectionDelegate delegate = _FakeEditableTextState ();
1192
+
1193
+ test ('Basic TextSpan Hit testing' , () {
1194
+ final TextSpan textSpanA = TextSpan (text: 'A' * 10 );
1195
+ const TextSpan textSpanBC = TextSpan (text: 'BC' , style: TextStyle (letterSpacing: 26.0 ));
1196
+
1197
+ final TextSpan text = TextSpan (
1198
+ text: '' ,
1199
+ style: const TextStyle (fontSize: 10.0 ),
1200
+ children: < InlineSpan > [textSpanA, textSpanBC],
1201
+ );
1202
+
1203
+ final RenderEditable renderEditable = RenderEditable (
1204
+ text: text,
1205
+ maxLines: null ,
1206
+ startHandleLayerLink: LayerLink (),
1207
+ endHandleLayerLink: LayerLink (),
1208
+ textDirection: TextDirection .ltr,
1209
+ offset: ViewportOffset .fixed (0.0 ),
1210
+ textSelectionDelegate: delegate,
1211
+ selection: const TextSelection .collapsed (offset: 0 ),
1212
+ );
1213
+ layout (renderEditable, constraints: BoxConstraints .tightFor (width: 100.0 + _caretMarginOf (renderEditable)));
1214
+
1215
+ BoxHitTestResult result;
1216
+
1217
+ // Hit-testing the first line
1218
+ // First A
1219
+ expect (renderEditable.hitTest (result = BoxHitTestResult (), position: const Offset (5.0 , 5.0 )), isTrue);
1220
+ expect (result.path.map ((HitTestEntry <HitTestTarget > entry) => entry.target).whereType <TextSpan >(), < TextSpan > [textSpanA]);
1221
+ // The last A.
1222
+ expect (renderEditable.hitTest (result = BoxHitTestResult (), position: const Offset (95.0 , 5.0 )), isTrue);
1223
+ expect (result.path.map ((HitTestEntry <HitTestTarget > entry) => entry.target).whereType <TextSpan >(), < TextSpan > [textSpanA]);
1224
+ // Far away from the line.
1225
+ expect (renderEditable.hitTest (result = BoxHitTestResult (), position: const Offset (200.0 , 5.0 )), isFalse);
1226
+ expect (result.path.map ((HitTestEntry <HitTestTarget > entry) => entry.target).whereType <TextSpan >(), < TextSpan > []);
1227
+
1228
+ // Hit-testing the second line
1229
+ // Tapping on B (startX = letter-spacing / 2 = 13.0).
1230
+ expect (renderEditable.hitTest (result = BoxHitTestResult (), position: const Offset (18.0 , 15.0 )), isTrue);
1231
+ expect (result.path.map ((HitTestEntry <HitTestTarget > entry) => entry.target).whereType <TextSpan >(), < TextSpan > [textSpanBC]);
1232
+
1233
+ // Between B and C, with large letter-spacing.
1234
+ expect (renderEditable.hitTest (result = BoxHitTestResult (), position: const Offset (31.0 , 15.0 )), isTrue);
1235
+ expect (result.path.map ((HitTestEntry <HitTestTarget > entry) => entry.target).whereType <TextSpan >(), < TextSpan > [textSpanBC]);
1236
+
1237
+ // On C.
1238
+ expect (renderEditable.hitTest (result = BoxHitTestResult (), position: const Offset (54.0 , 15.0 )), isTrue);
1239
+ expect (result.path.map ((HitTestEntry <HitTestTarget > entry) => entry.target).whereType <TextSpan >(), < TextSpan > [textSpanBC]);
1240
+
1241
+ // After C.
1242
+ expect (renderEditable.hitTest (result = BoxHitTestResult (), position: const Offset (100.0 , 15.0 )), isTrue);
1243
+ expect (result.path.map ((HitTestEntry <HitTestTarget > entry) => entry.target).whereType <TextSpan >(), < TextSpan > []);
1244
+
1245
+ // Not even remotely close.
1246
+ expect (renderEditable.hitTest (result = BoxHitTestResult (), position: const Offset (9999.0 , 9999.0 )), isFalse);
1247
+ expect (result.path.map ((HitTestEntry <HitTestTarget > entry) => entry.target).whereType <TextSpan >(), < TextSpan > []);
1248
+ });
1249
+
1250
+ test ('TextSpan Hit testing with text justification' , () {
1251
+ const TextSpan textSpanA = TextSpan (text: 'A ' ); // The space is a word break.
1252
+ const TextSpan textSpanB = TextSpan (text: 'B\u 200B' ); // The zero-width space is used as a line break.
1253
+ final TextSpan textSpanC = TextSpan (text: 'C' * 10 ); // The third span starts a new line since it's too long for the first line.
1254
+
1255
+ // The text should look like:
1256
+ // A B
1257
+ // CCCCCCCCCC
1258
+ final TextSpan text = TextSpan (
1259
+ text: '' ,
1260
+ style: const TextStyle (fontSize: 10.0 ),
1261
+ children: < InlineSpan > [textSpanA, textSpanB, textSpanC],
1262
+ );
1263
+ final RenderEditable renderEditable = RenderEditable (
1264
+ text: text,
1265
+ maxLines: null ,
1266
+ startHandleLayerLink: LayerLink (),
1267
+ endHandleLayerLink: LayerLink (),
1268
+ textDirection: TextDirection .ltr,
1269
+ textAlign: TextAlign .justify,
1270
+ offset: ViewportOffset .fixed (0.0 ),
1271
+ textSelectionDelegate: delegate,
1272
+ selection: const TextSelection .collapsed (offset: 0 ),
1273
+ );
1274
+
1275
+ layout (renderEditable, constraints: BoxConstraints .tightFor (width: 100.0 + _caretMarginOf (renderEditable)));
1276
+ BoxHitTestResult result;
1277
+
1278
+ // Tapping on A.
1279
+ expect (renderEditable.hitTest (result = BoxHitTestResult (), position: const Offset (5.0 , 5.0 )), isTrue);
1280
+ expect (result.path.map ((HitTestEntry <HitTestTarget > entry) => entry.target).whereType <TextSpan >(), < TextSpan > [textSpanA]);
1281
+
1282
+ // Between A and B.
1283
+ expect (renderEditable.hitTest (result = BoxHitTestResult (), position: const Offset (50.0 , 5.0 )), isTrue);
1284
+ expect (result.path.map ((HitTestEntry <HitTestTarget > entry) => entry.target).whereType <TextSpan >(), < TextSpan > [textSpanA]);
1285
+
1286
+ // On B.
1287
+ expect (renderEditable.hitTest (result = BoxHitTestResult (), position: const Offset (95.0 , 5.0 )), isTrue);
1288
+ expect (result.path.map ((HitTestEntry <HitTestTarget > entry) => entry.target).whereType <TextSpan >(), < TextSpan > [textSpanB]);
1289
+ });
1290
+
1187
1291
test ('hits correct TextSpan when not scrolled' , () {
1188
- final TextSelectionDelegate delegate = _FakeEditableTextState ();
1189
1292
final RenderEditable editable = RenderEditable (
1190
1293
text: const TextSpan (
1191
1294
style: TextStyle (height: 1.0 , fontSize: 10.0 ),
@@ -1692,7 +1795,8 @@ void main() {
1692
1795
// Prepare for painting after layout.
1693
1796
pumpFrame (phase: EnginePhase .compositingBits);
1694
1797
BoxHitTestResult result = BoxHitTestResult ();
1695
- editable.hitTest (result, position: Offset .zero);
1798
+ // The WidgetSpans have a height of 14.0, so "test" has a y offset of 4.0.
1799
+ editable.hitTest (result, position: const Offset (1.0 , 5.0 ));
1696
1800
// We expect two hit test entries in the path because the RenderEditable
1697
1801
// will add itself as well.
1698
1802
expect (result.path, hasLength (2 ));
@@ -1702,7 +1806,7 @@ void main() {
1702
1806
// Only testing the RenderEditable entry here once, not anymore below.
1703
1807
expect (result.path.last.target, isA <RenderEditable >());
1704
1808
result = BoxHitTestResult ();
1705
- editable.hitTest (result, position: const Offset (15.0 , 0 .0 ));
1809
+ editable.hitTest (result, position: const Offset (15.0 , 5 .0 ));
1706
1810
expect (result.path, hasLength (2 ));
1707
1811
target = result.path.first.target;
1708
1812
expect (target, isA <TextSpan >());
@@ -1775,7 +1879,8 @@ void main() {
1775
1879
// Prepare for painting after layout.
1776
1880
pumpFrame (phase: EnginePhase .compositingBits);
1777
1881
BoxHitTestResult result = BoxHitTestResult ();
1778
- editable.hitTest (result, position: Offset .zero);
1882
+ // The WidgetSpans have a height of 14.0, so "test" has a y offset of 4.0.
1883
+ editable.hitTest (result, position: const Offset (0.0 , 4.0 ));
1779
1884
// We expect two hit test entries in the path because the RenderEditable
1780
1885
// will add itself as well.
1781
1886
expect (result.path, hasLength (2 ));
@@ -1785,13 +1890,14 @@ void main() {
1785
1890
// Only testing the RenderEditable entry here once, not anymore below.
1786
1891
expect (result.path.last.target, isA <RenderEditable >());
1787
1892
result = BoxHitTestResult ();
1788
- editable.hitTest (result, position: const Offset (15.0 , 0 .0 ));
1893
+ editable.hitTest (result, position: const Offset (15.0 , 4 .0 ));
1789
1894
expect (result.path, hasLength (2 ));
1790
1895
target = result.path.first.target;
1791
1896
expect (target, isA <TextSpan >());
1792
1897
expect ((target as TextSpan ).text, text);
1793
1898
1794
1899
result = BoxHitTestResult ();
1900
+ // "test" is 40 pixel wide.
1795
1901
editable.hitTest (result, position: const Offset (41.0 , 0.0 ));
1796
1902
expect (result.path, hasLength (3 ));
1797
1903
target = result.path.first.target;
@@ -1814,7 +1920,7 @@ void main() {
1814
1920
1815
1921
result = BoxHitTestResult ();
1816
1922
editable.hitTest (result, position: const Offset (5.0 , 15.0 ));
1817
- expect (result.path, hasLength (2 ));
1923
+ expect (result.path, hasLength (1 )); // Only the RenderEditable.
1818
1924
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61020
1819
1925
});
1820
1926
0 commit comments