| // Copyright 2014 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 'dart:ui'; |
| |
| import 'package:flutter/gestures.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| void main() { |
| test('TextSpan equals', () { |
| const TextSpan a1 = TextSpan(text: 'a'); |
| const TextSpan a2 = TextSpan(text: 'a'); |
| const TextSpan b1 = TextSpan(children: <TextSpan>[ a1 ]); |
| const TextSpan b2 = TextSpan(children: <TextSpan>[ a2 ]); |
| const TextSpan c1 = TextSpan(); |
| const TextSpan c2 = TextSpan(); |
| |
| expect(a1 == a2, isTrue); |
| expect(b1 == b2, isTrue); |
| expect(c1 == c2, isTrue); |
| |
| expect(a1 == b2, isFalse); |
| expect(b1 == c2, isFalse); |
| expect(c1 == a2, isFalse); |
| |
| expect(a1 == c2, isFalse); |
| expect(b1 == a2, isFalse); |
| expect(c1 == b2, isFalse); |
| |
| void callback1(PointerEnterEvent _) {} |
| void callback2(PointerEnterEvent _) {} |
| |
| final TextSpan d1 = TextSpan(text: 'a', onEnter: callback1); |
| final TextSpan d2 = TextSpan(text: 'a', onEnter: callback1); |
| final TextSpan d3 = TextSpan(text: 'a', onEnter: callback2); |
| final TextSpan e1 = TextSpan(text: 'a', onEnter: callback2, mouseCursor: SystemMouseCursors.forbidden); |
| final TextSpan e2 = TextSpan(text: 'a', onEnter: callback2, mouseCursor: SystemMouseCursors.forbidden); |
| |
| expect(a1 == d1, isFalse); |
| expect(d1 == d2, isTrue); |
| expect(d2 == d3, isFalse); |
| expect(d3 == e1, isFalse); |
| expect(e1 == e2, isTrue); |
| }); |
| |
| test('TextSpan toStringDeep', () { |
| const TextSpan test = TextSpan( |
| text: 'a', |
| style: TextStyle( |
| fontSize: 10.0, |
| ), |
| children: <TextSpan>[ |
| TextSpan( |
| text: 'b', |
| children: <TextSpan>[ |
| TextSpan(), |
| ], |
| ), |
| TextSpan( |
| text: 'c', |
| ), |
| ], |
| ); |
| expect(test.toStringDeep(), equals( |
| 'TextSpan:\n' |
| ' inherit: true\n' |
| ' size: 10.0\n' |
| ' "a"\n' |
| ' TextSpan:\n' |
| ' "b"\n' |
| ' TextSpan:\n' |
| ' (empty)\n' |
| ' TextSpan:\n' |
| ' "c"\n', |
| )); |
| }); |
| |
| test('TextSpan toStringDeep for mouse', () { |
| const TextSpan test1 = TextSpan( |
| text: 'a', |
| ); |
| expect(test1.toStringDeep(), equals( |
| 'TextSpan:\n' |
| ' "a"\n', |
| )); |
| |
| final TextSpan test2 = TextSpan( |
| text: 'a', |
| onEnter: (_) {}, |
| onExit: (_) {}, |
| mouseCursor: SystemMouseCursors.forbidden, |
| ); |
| expect(test2.toStringDeep(), equals( |
| 'TextSpan:\n' |
| ' "a"\n' |
| ' callbacks: enter, exit\n' |
| ' mouseCursor: SystemMouseCursor(forbidden)\n', |
| )); |
| }); |
| |
| |
| test('TextSpan toPlainText', () { |
| const TextSpan textSpan = TextSpan( |
| text: 'a', |
| children: <TextSpan>[ |
| TextSpan(text: 'b'), |
| TextSpan(text: 'c'), |
| ], |
| ); |
| expect(textSpan.toPlainText(), 'abc'); |
| }); |
| |
| test('WidgetSpan toPlainText', () { |
| const TextSpan textSpan = TextSpan( |
| text: 'a', |
| children: <InlineSpan>[ |
| TextSpan(text: 'b'), |
| WidgetSpan(child: SizedBox(width: 10, height: 10)), |
| TextSpan(text: 'c'), |
| ], |
| ); |
| expect(textSpan.toPlainText(), 'ab\uFFFCc'); |
| }); |
| |
| test('TextSpan toPlainText with semanticsLabel', () { |
| const TextSpan textSpan = TextSpan( |
| text: 'a', |
| children: <TextSpan>[ |
| TextSpan(text: 'b', semanticsLabel: 'foo'), |
| TextSpan(text: 'c'), |
| ], |
| ); |
| expect(textSpan.toPlainText(), 'afooc'); |
| expect(textSpan.toPlainText(includeSemanticsLabels: false), 'abc'); |
| }); |
| |
| test('TextSpan widget change test', () { |
| const TextSpan textSpan1 = TextSpan( |
| text: 'a', |
| children: <InlineSpan>[ |
| TextSpan(text: 'b'), |
| WidgetSpan(child: SizedBox(width: 10, height: 10)), |
| TextSpan(text: 'c'), |
| ], |
| ); |
| |
| const TextSpan textSpan2 = TextSpan( |
| text: 'a', |
| children: <InlineSpan>[ |
| TextSpan(text: 'b'), |
| WidgetSpan(child: SizedBox(width: 10, height: 10)), |
| TextSpan(text: 'c'), |
| ], |
| ); |
| |
| const TextSpan textSpan3 = TextSpan( |
| text: 'a', |
| children: <InlineSpan>[ |
| TextSpan(text: 'b'), |
| WidgetSpan(child: SizedBox(width: 11, height: 10)), |
| TextSpan(text: 'c'), |
| ], |
| ); |
| |
| const TextSpan textSpan4 = TextSpan( |
| text: 'a', |
| children: <InlineSpan>[ |
| TextSpan(text: 'b'), |
| WidgetSpan(child: Text('test')), |
| TextSpan(text: 'c'), |
| ], |
| ); |
| |
| const TextSpan textSpan5 = TextSpan( |
| text: 'a', |
| children: <InlineSpan>[ |
| TextSpan(text: 'b'), |
| WidgetSpan(child: Text('different!')), |
| TextSpan(text: 'c'), |
| ], |
| ); |
| |
| const TextSpan textSpan6 = TextSpan( |
| text: 'a', |
| children: <InlineSpan>[ |
| TextSpan(text: 'b'), |
| WidgetSpan( |
| child: SizedBox(width: 10, height: 10), |
| alignment: PlaceholderAlignment.top, |
| ), |
| TextSpan(text: 'c'), |
| ], |
| ); |
| |
| expect(textSpan1.compareTo(textSpan3), RenderComparison.layout); |
| expect(textSpan1.compareTo(textSpan4), RenderComparison.layout); |
| expect(textSpan1.compareTo(textSpan1), RenderComparison.identical); |
| expect(textSpan2.compareTo(textSpan2), RenderComparison.identical); |
| expect(textSpan3.compareTo(textSpan3), RenderComparison.identical); |
| expect(textSpan2.compareTo(textSpan3), RenderComparison.layout); |
| expect(textSpan4.compareTo(textSpan5), RenderComparison.layout); |
| expect(textSpan3.compareTo(textSpan5), RenderComparison.layout); |
| expect(textSpan2.compareTo(textSpan5), RenderComparison.layout); |
| expect(textSpan1.compareTo(textSpan5), RenderComparison.layout); |
| expect(textSpan1.compareTo(textSpan6), RenderComparison.layout); |
| }); |
| |
| test('TextSpan nested widget change test', () { |
| const TextSpan textSpan1 = TextSpan( |
| text: 'a', |
| children: <InlineSpan>[ |
| TextSpan(text: 'b'), |
| WidgetSpan( |
| child: Text.rich( |
| TextSpan( |
| children: <InlineSpan>[ |
| WidgetSpan(child: SizedBox(width: 10, height: 10)), |
| TextSpan(text: 'The sky is falling :)'), |
| ], |
| ), |
| ), |
| ), |
| TextSpan(text: 'c'), |
| ], |
| ); |
| |
| const TextSpan textSpan2 = TextSpan( |
| text: 'a', |
| children: <InlineSpan>[ |
| TextSpan(text: 'b'), |
| WidgetSpan( |
| child: Text.rich( |
| TextSpan( |
| children: <InlineSpan>[ |
| WidgetSpan(child: SizedBox(width: 10, height: 11)), |
| TextSpan(text: 'The sky is falling :)'), |
| ], |
| ), |
| ), |
| ), |
| TextSpan(text: 'c'), |
| ], |
| ); |
| |
| expect(textSpan1.compareTo(textSpan2), RenderComparison.layout); |
| expect(textSpan1.compareTo(textSpan1), RenderComparison.identical); |
| expect(textSpan2.compareTo(textSpan2), RenderComparison.identical); |
| }); |
| |
| test('GetSpanForPosition with WidgetSpan', () { |
| const TextSpan textSpan = TextSpan( |
| text: 'a', |
| children: <InlineSpan>[ |
| TextSpan(text: 'b'), |
| WidgetSpan( |
| child: Text.rich( |
| TextSpan( |
| children: <InlineSpan>[ |
| WidgetSpan(child: SizedBox(width: 10, height: 10)), |
| TextSpan(text: 'The sky is falling :)'), |
| ], |
| ), |
| ), |
| ), |
| TextSpan(text: 'c'), |
| ], |
| ); |
| |
| expect(textSpan.getSpanForPosition(const TextPosition(offset: 0)).runtimeType, TextSpan); |
| expect(textSpan.getSpanForPosition(const TextPosition(offset: 1)).runtimeType, TextSpan); |
| expect(textSpan.getSpanForPosition(const TextPosition(offset: 2)).runtimeType, WidgetSpan); |
| expect(textSpan.getSpanForPosition(const TextPosition(offset: 3)).runtimeType, TextSpan); |
| }); |
| |
| test('TextSpan computeSemanticsInformation', () { |
| final List<InlineSpanSemanticsInformation> collector = <InlineSpanSemanticsInformation>[]; |
| const TextSpan(text: 'aaa', semanticsLabel: 'bbb').computeSemanticsInformation(collector); |
| expect(collector[0].text, 'aaa'); |
| expect(collector[0].semanticsLabel, 'bbb'); |
| }); |
| |
| testWidgets('handles mouse cursor', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| const Directionality( |
| textDirection: TextDirection.ltr, |
| child: Center( |
| child: Text.rich( |
| TextSpan( |
| text: 'xxxxx', |
| children: <InlineSpan>[ |
| TextSpan( |
| text: 'yyyyy', |
| mouseCursor: SystemMouseCursors.forbidden, |
| ), |
| TextSpan( |
| text: 'xxxxx', |
| ), |
| ], |
| ), |
| textAlign: TextAlign.center, |
| ), |
| ), |
| ), |
| ); |
| |
| final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
| await gesture.addPointer(); |
| |
| await gesture.moveTo(tester.getCenter(find.byType(RichText)) - const Offset(40, 0)); |
| expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic); |
| |
| await gesture.moveTo(tester.getCenter(find.byType(RichText))); |
| expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden); |
| |
| await gesture.moveTo(tester.getCenter(find.byType(RichText)) + const Offset(40, 0)); |
| expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic); |
| }); |
| |
| testWidgets('handles onEnter and onExit', (WidgetTester tester) async { |
| final List<PointerEvent> logEvents = <PointerEvent>[]; |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Center( |
| child: Text.rich( |
| TextSpan( |
| text: 'xxxxx', |
| children: <InlineSpan>[ |
| TextSpan( |
| text: 'yyyyy', |
| onEnter: (PointerEnterEvent event) { |
| logEvents.add(event); |
| }, |
| onExit: (PointerExitEvent event) { |
| logEvents.add(event); |
| }, |
| ), |
| const TextSpan( |
| text: 'xxxxx', |
| ), |
| ], |
| ), |
| textAlign: TextAlign.center, |
| ), |
| ), |
| ), |
| ); |
| |
| final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
| await gesture.addPointer(); |
| |
| await gesture.moveTo(tester.getCenter(find.byType(RichText)) - const Offset(40, 0)); |
| expect(logEvents, isEmpty); |
| |
| await gesture.moveTo(tester.getCenter(find.byType(RichText))); |
| expect(logEvents.length, 1); |
| expect(logEvents[0], isA<PointerEnterEvent>()); |
| |
| await gesture.moveTo(tester.getCenter(find.byType(RichText)) + const Offset(40, 0)); |
| expect(logEvents.length, 2); |
| expect(logEvents[1], isA<PointerExitEvent>()); |
| }); |
| |
| testWidgets('TextSpan can compute StringAttributes', (WidgetTester tester) async { |
| const TextSpan span = TextSpan( |
| text: 'aaaaa', |
| spellOut: true, |
| children: <InlineSpan>[ |
| TextSpan(text: 'yyyyy', locale: Locale('es', 'MX')), |
| TextSpan( |
| text: 'xxxxx', |
| spellOut: false, |
| children: <InlineSpan>[ |
| TextSpan(text: 'zzzzz'), |
| TextSpan(text: 'bbbbb', spellOut: true), |
| ] |
| ), |
| ], |
| ); |
| final List<InlineSpanSemanticsInformation> collector = <InlineSpanSemanticsInformation>[]; |
| span.computeSemanticsInformation(collector); |
| expect(collector.length, 5); |
| expect(collector[0].stringAttributes.length, 1); |
| expect(collector[0].stringAttributes[0], isA<SpellOutStringAttribute>()); |
| expect(collector[0].stringAttributes[0].range, const TextRange(start: 0, end: 5)); |
| expect(collector[1].stringAttributes.length, 2); |
| expect(collector[1].stringAttributes[0], isA<SpellOutStringAttribute>()); |
| expect(collector[1].stringAttributes[0].range, const TextRange(start: 0, end: 5)); |
| expect(collector[1].stringAttributes[1], isA<LocaleStringAttribute>()); |
| expect(collector[1].stringAttributes[1].range, const TextRange(start: 0, end: 5)); |
| final LocaleStringAttribute localeStringAttribute = collector[1].stringAttributes[1] as LocaleStringAttribute; |
| expect(localeStringAttribute.locale, const Locale('es', 'MX')); |
| expect(collector[2].stringAttributes.length, 0); |
| expect(collector[3].stringAttributes.length, 0); |
| expect(collector[4].stringAttributes.length, 1); |
| expect(collector[4].stringAttributes[0], isA<SpellOutStringAttribute>()); |
| expect(collector[4].stringAttributes[0].range, const TextRange(start: 0, end: 5)); |
| |
| final List<InlineSpanSemanticsInformation> combined = combineSemanticsInfo(collector); |
| expect(combined.length, 1); |
| expect(combined[0].stringAttributes.length, 4); |
| expect(combined[0].stringAttributes[0], isA<SpellOutStringAttribute>()); |
| expect(combined[0].stringAttributes[0].range, const TextRange(start: 0, end: 5)); |
| expect(combined[0].stringAttributes[1], isA<SpellOutStringAttribute>()); |
| expect(combined[0].stringAttributes[1].range, const TextRange(start: 5, end: 10)); |
| expect(combined[0].stringAttributes[2], isA<LocaleStringAttribute>()); |
| expect(combined[0].stringAttributes[2].range, const TextRange(start: 5, end: 10)); |
| final LocaleStringAttribute combinedLocaleStringAttribute = combined[0].stringAttributes[2] as LocaleStringAttribute; |
| expect(combinedLocaleStringAttribute.locale, const Locale('es', 'MX')); |
| expect(combined[0].stringAttributes[3], isA<SpellOutStringAttribute>()); |
| expect(combined[0].stringAttributes[3].range, const TextRange(start: 20, end: 25)); |
| }); |
| } |