diff --git a/tests/functions/make_clickable_email_test.php b/tests/functions/make_clickable_email_test.php
new file mode 100644
index 0000000000..4c802d0487
--- /dev/null
+++ b/tests/functions/make_clickable_email_test.php
@@ -0,0 +1,222 @@
+
+* @license GNU General Public License, version 2 (GPL-2.0)
+*
+* For full copyright and license information, please see
+* the docs/CREDITS.txt file.
+*
+*/
+
+require_once dirname(__FILE__) . '/../../phpBB/includes/functions.php';
+require_once dirname(__FILE__) . '/../../phpBB/includes/functions_content.php';
+
+class phpbb_functions_make_clickable_email_test extends phpbb_test_case
+{
+ protected function setUp()
+ {
+ parent::setUp();
+
+ global $config, $user, $request;
+ $user = new phpbb_mock_user();
+ $request = new phpbb_mock_request();
+ }
+
+ /**
+ * 'e' tag for email addresses html
+ **/
+ public function data_test_make_clickable_email_positive()
+ {
+ return array(
+ array(
+ 'nobody@phpbb.com',
+ 'nobody@phpbb.com'
+ ),
+ array(
+ 'Nobody@sub.phpbb.com',
+ 'Nobody@sub.phpbb.com'
+ ),
+ array(
+ 'alice.bob@foo.phpbb.com',
+ 'alice.bob@foo.phpbb.com'
+ ),
+ array(
+ 'alice-foo@bar.phpbb.com',
+ 'alice-foo@bar.phpbb.com'
+ ),
+ array(
+ 'alice_foo@bar.phpbb.com',
+ 'alice_foo@bar.phpbb.com'
+ ),
+ array(
+ 'alice+tag@foo.phpbb.com',
+ 'alice+tag@foo.phpbb.com'
+ ),
+ array(
+ 'alice&tag@foo.phpbb.com',
+ 'alice&tag@foo.phpbb.com'
+ ),
+ array(
+ 'alice@phpbb.australia',
+ 'alice@phpbb.australia'
+ ),
+
+ // Test shortened text for email > 55 characters long
+ // Email text should be turned into: first 39 chars + ' ... ' + last 10 chars
+ array(
+ 'alice@phpbb.topZlevelZdomainZnamesZcanZbeZupZtoZsixtyZthreeZcharactersZlong',
+ 'alice@phpbb.topZlevelZdomainZnamesZcanZ ... ctersZlong'
+ ),
+ array(
+ 'l3tt3rsAndNumb3rs@domain.com',
+ 'l3tt3rsAndNumb3rs@domain.com'
+ ),
+ array(
+ 'has-dash@domain.com',
+ 'has-dash@domain.com'
+ ),
+ array(
+ 'hasApostrophe.o\'leary@domain.org',
+ 'hasApostrophe.o\'leary@domain.org'
+ ),
+ array(
+ 'uncommonTLD@domain.museum',
+ 'uncommonTLD@domain.museum'
+ ),
+ array(
+ 'uncommonTLD@domain.travel',
+ 'uncommonTLD@domain.travel'
+ ),
+ array(
+ 'uncommonTLD@domain.mobi',
+ 'uncommonTLD@domain.mobi'
+ ),
+ array(
+ 'countryCodeTLD@domain.uk',
+ 'countryCodeTLD@domain.uk'
+ ),
+ array(
+ 'countryCodeTLD@domain.rw',
+ 'countryCodeTLD@domain.rw'
+ ),
+ array(
+ 'numbersInDomain@911.com',
+ 'numbersInDomain@911.com'
+ ),
+ array(
+ 'underscore_inLocal@domain.net',
+ 'underscore_inLocal@domain.net'
+ ),
+ array(
+ 'IPInsteadOfDomain@127.0.0.1',
+ 'IPInsteadOfDomain@127.0.0.1'
+ ),
+ array(
+ 'IPAndPort@127.0.0.1:25',
+ 'IPAndPort@127.0.0.1:25'
+ ),
+ array(
+ 'subdomain@sub.domain.com',
+ 'subdomain@sub.domain.com'
+ ),
+ array(
+ 'local@dash-inDomain.com',
+ 'local@dash-inDomain.com'
+ ),
+ array(
+ 'dot.inLocal@foo.com',
+ 'dot.inLocal@foo.com'
+ ),
+ array(
+ 'a@singleLetterLocal.org',
+ 'a@singleLetterLocal.org'
+ ),
+ array(
+ 'singleLetterDomain@x.org',
+ 'singleLetterDomain@x.org'
+ ),
+ array(
+ '&*=?^+{}\'~@validCharsInLocal.net',
+ '&*=?^+{}\'~@validCharsInLocal.net'
+ ),
+ array(
+ 'foor@bar.newTLD',
+ 'foor@bar.newTLD'
+ ),
+ );
+ }
+
+ public function data_test_make_clickable_email_negative()
+ {
+ return array(
+ array('foo.example.com'), // @ is missing
+ array('.foo.example.com'), // . as first character
+ array('Foo.@example.com'), // . is last in local part
+ array('foo..123@example.com'), // . doubled
+ array('a@b@c@example.com'), // @ doubled
+
+ // Emails with invalid characters
+ // (only 'valid' pieces having localparts prepended with one of the \n \t ( > chars should parsed if any)
+ array('()[]\;:,<>@example.com'), // invalid characters
+ array('abc(def@example.com', 'abc(def@example.com'), // invalid character (
+ array('abc)def@example.com'), // invalid character )
+ array('abc[def@example.com'), // invalid character [
+ array('abc]def@example.com'), // invalid character ]
+ array('abc\def@example.com'), // invalid character \
+ array('abc;def@example.com'), // invalid character ;
+ array('abc:def@example.com'), // invalid character :
+ array('abc,def@example.com'), // invalid character ,
+ array('abcdef@example.com', 'abc>def@example.com'), // invalid character >
+
+ // http://fightingforalostcause.net/misc/2006/compare-email-regex.php
+ array('missingDomain@.com'),
+ array('@missingLocal.org'),
+ array('missingatSign.net'),
+ array('missingDot@com'),
+ array('two@@signs.com'),
+ // Trailing colon is ignored
+ array('colonButNoPort@127.0.0.1:', 'colonButNoPort@127.0.0.1:'),
+
+ array(''),
+ // Trailing part after the 3rd dot is ignored
+ array('someone-else@127.0.0.1.26', 'someone-else@127.0.0.1.26'),
+
+ array('.localStartsWithDot@domain.com'),
+ array('localEndsWithDot.@domain.com'),
+ array('two..consecutiveDots@domain.com'),
+ array('domainStartsWithDash@-domain.com'),
+ array('domainEndsWithDash@domain-.com'),
+ array('numbersInTLD@domain.c0m'),
+ array('missingTLD@domain.'),
+ array('! "#$%(),/;<>[]`|@invalidCharsInLocal.org'),
+ array('invalidCharsInDomain@! "#$%(),/;<>_[]`|.org'),
+ array('local@SecondLevelDomainNamesAreInvalidIfTheyAreLongerThan64Charactersss.org'),
+ // The domain zone name part after the 63rd char is ignored
+ array(
+ 'alice@phpbb.topZlevelZdomainZnamesZcanZbeZupZtoZsixtyZthreeZcharactersZlongZ',
+ 'alice@phpbb.topZlevelZdomainZnamesZcanZ ... ctersZlongZ'
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider data_test_make_clickable_email_positive
+ */
+ public function test_email_matching_positive($email, $expected)
+ {
+ $this->assertSame($expected, make_clickable($email));
+ }
+
+ /**
+ * @dataProvider data_test_make_clickable_email_negative
+ */
+ public function test_email_matching_negative($email, $expected = null)
+ {
+ $expected = ($expected) ?: $email;
+ $this->assertSame($expected, make_clickable($email));
+ }
+}