From 67bb7a548ab07cf541010db838a88f330f8712ed Mon Sep 17 00:00:00 2001 From: iamstanlee Date: Thu, 3 Jun 2021 17:06:40 -0700 Subject: [PATCH] add regex tests --- lib/linkify_text.dart | 1 + lib/src/linkify.dart | 75 +++++++++++++++++++++++++++------- lib/src/utils/regex.dart | 43 ++++++++++++++++--- lib/src/utils/utils.dart | 1 - test/regex_test.dart | 49 ---------------------- test/utils/regex_test.dart | 84 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 183 insertions(+), 70 deletions(-) delete mode 100644 lib/src/utils/utils.dart delete mode 100644 test/regex_test.dart create mode 100644 test/utils/regex_test.dart diff --git a/lib/linkify_text.dart b/lib/linkify_text.dart index facb939..61ea2d0 100644 --- a/lib/linkify_text.dart +++ b/lib/linkify_text.dart @@ -1,3 +1,4 @@ +/// A lightweight flutter package to linkify texts containing urls, emails and hashtags. library linkify_text; export 'src/linkify.dart'; diff --git a/lib/src/linkify.dart b/lib/src/linkify.dart index 54912c2..3f981ad 100644 --- a/lib/src/linkify.dart +++ b/lib/src/linkify.dart @@ -1,24 +1,72 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; +import 'package:linkify_text/src/utils/regex.dart'; -enum LinkOptions { url, email, hashTag } +enum LinkOption { url, email, hashTag } -TextSpan formatText( - {required String text, TextStyle? textStyle, TextStyle? linkStyle}) { - RegExp re = RegExp( - r"(((ht|f)tp(s?)://)|www.)[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*/?([a-zA-Z0-9\-.\?,'/\\\+&%\$#_=]*)?"); +/// Linkify [text] containing urls, emails or hashtag +class LinkifyText extends StatelessWidget { + const LinkifyText(this.text, + {this.textStyle, this.linkStyle, this.options, this.onTap, Key? key}) + : super(key: key); - // return the full text if there's no match - if (!re.hasMatch(text)) { - return TextSpan(text: text, style: textStyle); + /// text to be linkified + final String text; + + /// [textStyle] applied the text + final TextStyle? textStyle; + + /// [textStyle] added to the formatted links in the text + final TextStyle? linkStyle; + + /// called when a formatted link is pressed, it returns the link as a parameter + /// ```dart + /// LinkifyText("#helloWorld", onTap: (value) { + /// // do stuff with value + /// print("$value hashtag was tapped"); + /// }); + /// ``` + final void Function(String)? onTap; + + /// option to override the links to be formatted in the text, defaults to `[LinkOption.url]` + /// so only urls are being linkified in the text + final List? options; + + @override + Widget build(BuildContext context) { + // TODO: add all Text properties + return Text.rich( + formatText( + text: text, + linkStyle: linkStyle, + onTap: onTap, + options: options, + ), + key: key, + style: linkStyle, + ); } +} - List texts = text.split(re); +TextSpan formatText({ + String text = '', + TextStyle? linkStyle, + List? options = const [LinkOption.url], + Function(String)? onTap, +}) { + RegExp _regExp = constructRegExpFromOptions(options!); + + // return the full text if there's no match or if empty + if (!_regExp.hasMatch(text) || text.isEmpty) return TextSpan(text: text); + + List texts = text.split(_regExp); List spans = []; - List links = re.allMatches(text).toList(); + List links = _regExp.allMatches(text).toList(); for (String text in texts) { - spans.add(TextSpan(text: text, style: textStyle)); + spans.add(TextSpan( + text: text, + )); if (links.length > 0) { RegExpMatch match = links.removeAt(0); String link = match.input.substring(match.start, match.end); @@ -30,10 +78,7 @@ TextSpan formatText( style: linkStyle, recognizer: TapGestureRecognizer() ..onTap = () { - String l = link.startsWith('www') ? 'http://$link' : link; - if (l.endsWith('.') || l.endsWith(',') || l.endsWith(';')) { - l = l.substring(0, l.length - 1); - } + onTap!(link); }, ), ); diff --git a/lib/src/utils/regex.dart b/lib/src/utils/regex.dart index 17abebc..3c65489 100644 --- a/lib/src/utils/regex.dart +++ b/lib/src/utils/regex.dart @@ -1,7 +1,40 @@ -RegExp urlRegex = RegExp(r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+'); +import 'package:linkify_text/linkify_text.dart'; -RegExp hashtagRegex = - RegExp(r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+'); +String urlRegExp = r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+'; -RegExp emailRegex = RegExp( - r"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$"); +String hashtagRegExp = r'(#+[a-zA-Z0-9(_)]{1,})'; + +String emailRegExp = + r"([a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+)"; + +/// construct regexp. pattern from provided link options +RegExp constructRegExpFromOptions(List options) { + // default case where we always want to match url strings + if (options.length == 1 && options.first == LinkOption.url) + return RegExp(urlRegExp); + + StringBuffer _regexBuffer = StringBuffer(); + for (var i = 0; i < options.length; i++) { + final o = options[i]; + final isLast = i == options.length - 1; + switch (o) { + case LinkOption.url: + isLast + ? _regexBuffer.write("($urlRegExp)") + : _regexBuffer.write("($urlRegExp)|"); + break; + case LinkOption.hashTag: + isLast + ? _regexBuffer.write("($hashtagRegExp)") + : _regexBuffer.write("($hashtagRegExp)|"); + break; + case LinkOption.email: + isLast + ? _regexBuffer.write("($emailRegExp)") + : _regexBuffer.write("($emailRegExp)|"); + break; + default: + } + } + return RegExp(_regexBuffer.toString()); +} diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart deleted file mode 100644 index 145ff75..0000000 --- a/lib/src/utils/utils.dart +++ /dev/null @@ -1 +0,0 @@ - nullify(dynamic value) {} \ No newline at end of file diff --git a/test/regex_test.dart b/test/regex_test.dart deleted file mode 100644 index 64edf39..0000000 --- a/test/regex_test.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:linkify_text/src/utils/regex.dart'; - -// final text = -// "My website url: https://blasanka.github.io/Google search using: www.google.com, social media is facebook.com, http://example.com/method?param=flutterstackoverflow.com is my greatest website. DartPad share: https://github.com/dart-lang/dart-pad/wiki/Sharing-Guide, see this example and edit it here"; - -void main() { - group('Regular Expression', () { - test("Should match all emails", () { - List _emails = ["hello@world.com", "foo.bar@js.com"]; - _emails.forEach((e) { - bool hasMatch = emailRegex.hasMatch(e); - expect(hasMatch, true); - }); - }); - test("Should match all urls", () { - List _urls = [ - "http://domain.com", - "http://domain.com/", - "https://domain.com", - "https://domain.com/", - "https://www.domain.com", - "www.domain.com", - "https://domain.com/Google?", - "https://domain.com/Google/search", - "https://domain.com/Google?param=", - "https://domain.com/Google?param=helloworld", - "https://sub.domain.com/Google?param=helloworld#hash", - ]; - _urls.forEach((u) { - bool hasMatch = emailRegex.hasMatch(u); - expect(hasMatch, true); - }); - }); - test("Should match all hashtags", () { - List _hashtags = [ - "#1", - "#trending", - "#_trending", - "#TRENDING", - "#trending_topic", - ]; - _hashtags.forEach((h) { - bool hasMatch = emailRegex.hasMatch(h); - expect(hasMatch, true); - }); - }); - }); -} diff --git a/test/utils/regex_test.dart b/test/utils/regex_test.dart new file mode 100644 index 0000000..240329f --- /dev/null +++ b/test/utils/regex_test.dart @@ -0,0 +1,84 @@ +import 'package:linkify_text/linkify_text.dart'; +import 'package:linkify_text/src/utils/regex.dart'; +import 'package:test/test.dart'; + +// TODO: write more testsss +void main() { + group('Regular Expression', () { + /// test values + final _urlText = + "My website url: https://hello.com/GOOGLE search using: www.google.com, social media is facebook.com, http://example.com/method?param=fullstackoverflow.dev"; + final _hashtagText = "#helloWorld and #dev are trending"; + final _emailText = + "My email address is hey@stanleee.me and dev@gmail.com, yah!"; + final _text = _urlText + _hashtagText + _emailText; + + final _emails = ["hello@world.com", "foo.bar@js.com"]; + + final List _urls = [ + "http://domain.com", + "http://domain.com/", + "https://domain.com", + "https://domain.com/", + "https://www.domain.com", + "www.domain.com", + "https://domain.com/Google?", + "https://domain.com/Google/search", + "https://domain.com/Google?param=", + "https://domain.com/Google?param=helloworld", + "https://sub.domain.com/Google?param=helloworld#hash", + ]; + + final List _hashtags = [ + "#123", + "#H", + "#0", + "#trending", + "#_trending", + "#TRENDING", + "#trending_topic", + ]; + + /// + test("Should match all emails", () { + _emails.forEach((e) { + bool hasMatch = RegExp(emailRegExp).hasMatch(e); + expect(hasMatch, true); + }); + }); + + test("Should not match all emails", () { + List _emails = ["hello@world", "@js.com"]; + _emails.forEach((e) { + bool hasMatch = RegExp(emailRegExp).hasMatch(e); + expect(hasMatch, false); + }); + }); + + test("Should match all urls", () { + _urls.forEach((u) { + bool hasMatch = RegExp(urlRegExp).hasMatch(u); + expect(hasMatch, true); + }); + }); + + test("Should match all hashtags", () { + _hashtags.forEach((h) { + bool hasMatch = RegExp(hashtagRegExp).hasMatch(h); + expect(hasMatch, true); + }); + }); + + test("Should construct regex pattern from LinkOptions and match", () { + RegExp _urlRegExp = constructRegExpFromOptions([LinkOption.url]); + RegExp _hashtagRegExp = constructRegExpFromOptions([LinkOption.hashTag]); + RegExp _emailRegExp = constructRegExpFromOptions([LinkOption.email]); + RegExp _textRegExp = constructRegExpFromOptions( + [LinkOption.url, LinkOption.hashTag, LinkOption.email]); + expect(_urlRegExp.allMatches(_urlText).length, 4); + expect(_hashtagRegExp.allMatches(_hashtagText).length, 2); + expect(_emailRegExp.allMatches(_emailText).length, 2); + expect(_textRegExp.allMatches(_text).length, 8); + }); + }); +}