From ed2c0b1f6d4c46cd066a693a6c5251a6c02c9f2f Mon Sep 17 00:00:00 2001 From: DatDang Date: Wed, 6 Nov 2024 15:09:29 +0700 Subject: [PATCH] TF-3236 Widget test RichTextBuilder --- .../extensions/list_extensions.dart | 4 + .../views/text/rich_text_builder_test.dart | 280 ++++++++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 core/test/presentation/views/text/rich_text_builder_test.dart diff --git a/core/lib/presentation/extensions/list_extensions.dart b/core/lib/presentation/extensions/list_extensions.dart index 2656871ef..beb45f971 100644 --- a/core/lib/presentation/extensions/list_extensions.dart +++ b/core/lib/presentation/extensions/list_extensions.dart @@ -16,4 +16,8 @@ extension ListExtensions on List { validBuilder.build().toList(), validBuilder.build().toList()); } + + int countOccurrences(T value) { + return where((element) => element == value).length; + } } \ No newline at end of file diff --git a/core/test/presentation/views/text/rich_text_builder_test.dart b/core/test/presentation/views/text/rich_text_builder_test.dart new file mode 100644 index 000000000..f6a5f2bd2 --- /dev/null +++ b/core/test/presentation/views/text/rich_text_builder_test.dart @@ -0,0 +1,280 @@ +import 'package:core/presentation/extensions/list_extensions.dart'; +import 'package:core/presentation/views/text/rich_text_builder.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + const normalTextStyle = TextStyle( + color: Colors.black, + fontSize: 16, + ); + const highlightTextStyle = TextStyle( + color: Colors.red, + fontSize: 16, + ); + Widget buildRichText({ + required String textOrigin, + required String wordToStyle, + String? preMarkedText, + bool ensureHighlightVisible = false, + }) => MaterialApp(home: Scaffold( + body: RichTextBuilder( + textOrigin: textOrigin, + wordToStyle: wordToStyle, + styleOrigin: normalTextStyle, + styleWord: highlightTextStyle, + preMarkedText: preMarkedText, + ensureHighlightVisible: ensureHighlightVisible, + ), + )); + + Finder findNormalStyledText(String text, {int occurrence = 1}) { + final normalSpan = TextSpan(text: text, style: normalTextStyle); + return find.byWidgetPredicate((widget) { + return widget is RichText + && (widget.text as TextSpan).text == null + && ((widget.text as TextSpan).children!.first as TextSpan).children!.contains(normalSpan) + && ((widget.text as TextSpan).children!.first as TextSpan).children!.countOccurrences(normalSpan) == occurrence; + }); + } + + Finder findUnfocusedHighlightStyledText(String text, {int occurrence = 1}) { + final highlightSpan = TextSpan(text: text, style: highlightTextStyle); + return find.byWidgetPredicate((widget) { + return widget is RichText + && (widget.text as TextSpan).children != null + && ((widget.text as TextSpan).children!.first as TextSpan).children!.contains(highlightSpan) + && ((widget.text as TextSpan).children!.first as TextSpan).children!.countOccurrences(highlightSpan) == occurrence; + }); + } + + Finder findFocusedHighlightStyledText(String text) { + return find.byWidgetPredicate((widget) { + return widget is RichText + && (widget.text as TextSpan).text == null + && ((widget.text as TextSpan).children!.first as TextSpan) + .children + !.where((span) => span is WidgetSpan + && span.child is Text + && (span.child as Text).data == text + && (span.child as Text).style == highlightTextStyle + && span.child.key is GlobalKey) + .isNotEmpty; + }); + } + + group('rich text builder test:', () { + testWidgets( + 'should not highlight any text ' + 'when wordToStyle is empty ' + 'and preMarkedText is null', + (tester) async { + // arrange + const textOrigin = 'This is a test'; + final richText = buildRichText( + textOrigin: textOrigin, + wordToStyle: '', + ); + + // act + await tester.pumpWidget(richText); + + // assert + expect( + findNormalStyledText(textOrigin), + findsOneWidget, + ); + expect(findFocusedHighlightStyledText(''), findsNothing); + expect(findUnfocusedHighlightStyledText(''), findsNothing); + }); + + testWidgets( + 'should not highlight any text ' + 'when textOrigin is empty ' + 'and preMarkedText is null', + (tester) async { + // arrange + const wordToStyle = 'This is a test'; + final richText = buildRichText( + textOrigin: '', + wordToStyle: wordToStyle, + ); + + // act + await tester.pumpWidget(richText); + + // assert + expect( + findNormalStyledText(''), + findsOneWidget, + ); + expect(findFocusedHighlightStyledText(wordToStyle), findsNothing); + expect(findUnfocusedHighlightStyledText(wordToStyle), findsNothing); + }); + + testWidgets( + 'should highlight text with wordToStyle ' + 'when wordToStyle is not empty ' + 'and preMarkedText is null', + (tester) async { + // arrange + const textOrigin = 'This is a test of test'; + const wordToStyle = 'test'; + final richText = buildRichText( + textOrigin: textOrigin, + wordToStyle: wordToStyle, + ); + final highlightStyledTexts = wordToStyle + .allMatches(textOrigin) + .map((match) => match.group(0)!) + .toList(); + final focusedHighlightStyledText = highlightStyledTexts.first; + final unfocusedHighlightStyledTexts = List.from( + highlightStyledTexts.getRange(1, highlightStyledTexts.length)); + final normalStyledTexts = textOrigin + .split(wordToStyle) + ..removeWhere((text) => text.isEmpty); + + // act + await tester.pumpWidget(richText); + + // assert + for (final text in normalStyledTexts) { + expect( + findNormalStyledText( + text, + occurrence: normalStyledTexts.countOccurrences(text), + ), + findsOneWidget, + ); + } + for (final unfocusedHighlightStyledText in unfocusedHighlightStyledTexts) { + expect( + findUnfocusedHighlightStyledText( + unfocusedHighlightStyledText, + occurrence: unfocusedHighlightStyledTexts.countOccurrences(unfocusedHighlightStyledText), + ), + findsOneWidget, + ); + } + expect( + findFocusedHighlightStyledText(focusedHighlightStyledText), + findsOneWidget, + ); + }); + + testWidgets( + 'should highlight text with ' + 'when preMarkedText is not null ', + (tester) async { + // arrange + const preMarkedText = 'This is test ' + 'of test text with duplicate testThis is '; + final richText = buildRichText( + textOrigin: '', + wordToStyle: '', + preMarkedText: preMarkedText, + ); + + final highlightMatches = RegExp('(.*?)').allMatches(preMarkedText); + final highlightStyledTexts = highlightMatches + .map((match) => match.group(1)!) + .toList(); + final focusedHighlightStyledText = highlightStyledTexts.first; + final unfocusedHighlightStyledTexts = List.from( + highlightStyledTexts.getRange(1, highlightStyledTexts.length)); + final normalStyledTexts = []; + int lastIndex = 0; + for (final match in highlightMatches) { + normalStyledTexts.add(preMarkedText.substring(lastIndex, match.start)); + lastIndex = match.end; + } + normalStyledTexts + ..add(preMarkedText.substring(lastIndex)) + ..removeWhere((text) => text.isEmpty); + + // act + await tester.pumpWidget(richText); + + // assert + for (final text in normalStyledTexts) { + expect( + findNormalStyledText( + text, + occurrence: normalStyledTexts.countOccurrences(text), + ), + findsOneWidget, + ); + } + for (final unfocusedHighlightStyledText in unfocusedHighlightStyledTexts) { + expect( + findUnfocusedHighlightStyledText( + unfocusedHighlightStyledText, + occurrence: unfocusedHighlightStyledTexts.countOccurrences(unfocusedHighlightStyledText), + ), + findsOneWidget, + ); + } + expect( + findFocusedHighlightStyledText(focusedHighlightStyledText), + findsOneWidget, + ); + }); + + testWidgets( + 'should highlight text with ' + 'when preMarkedText is not null ' + 'and both textOrigin and wordToStyle are not empty', + (tester) async { + // arrange + const preMarkedText = 'This is test ' + 'of test text with duplicate test'; + final richText = buildRichText( + textOrigin: 'something something', + wordToStyle: 'some', + preMarkedText: preMarkedText, + ); + + final highlightMatches = RegExp('(.*?)').allMatches(preMarkedText); + final highlightStyledTexts = highlightMatches + .map((match) => match.group(1)!) + .toList(); + final focusedHighlightStyledText = highlightStyledTexts.first; + final unfocusedHighlightStyledTexts = List.from( + highlightStyledTexts.getRange(1, highlightStyledTexts.length)); + final normalStyledTexts = []; + int lastIndex = 0; + for (final match in highlightMatches) { + normalStyledTexts.add(preMarkedText.substring(lastIndex, match.start)); + lastIndex = match.end; + } + normalStyledTexts + ..add(preMarkedText.substring(lastIndex)) + ..removeWhere((text) => text.isEmpty); + + // act + await tester.pumpWidget(richText); + + // assert + for (final text in normalStyledTexts) { + expect( + findNormalStyledText(text), + findsOneWidget, + ); + } + for (final unfocusedHighlightStyledText in unfocusedHighlightStyledTexts) { + expect( + findUnfocusedHighlightStyledText( + unfocusedHighlightStyledText, + occurrence: unfocusedHighlightStyledTexts.countOccurrences(unfocusedHighlightStyledText), + ), + findsOneWidget, + ); + } + expect( + findFocusedHighlightStyledText(focusedHighlightStyledText), + findsOneWidget, + ); + }); + }); +} \ No newline at end of file