diff --git a/lib/main/utils/app_utils.dart b/lib/main/utils/app_utils.dart index 771c43d3f..7dc061d1e 100644 --- a/lib/main/utils/app_utils.dart +++ b/lib/main/utils/app_utils.dart @@ -52,7 +52,10 @@ class AppUtils { static TextDirection getCurrentDirection(BuildContext context) => Directionality.maybeOf(context) ?? TextDirection.ltr; static bool isEmailLocalhost(String email) { - return RegExp(r'^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@localhost$').hasMatch(email); + final normalized = email.trim(); + return RegExp( + r'^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@localhost$' + ).hasMatch(normalized); } static void copyEmailAddressToClipboard(BuildContext context, String emailAddress) { diff --git a/test/features/email/presentation/is_valid_email_test.dart b/test/features/email/presentation/is_valid_email_test.dart new file mode 100644 index 000000000..07cb8c9fa --- /dev/null +++ b/test/features/email/presentation/is_valid_email_test.dart @@ -0,0 +1,91 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; + +void main() { + group('EmailUtils::isValidEmail', () { + group('Valid public domain emails', () { + final validEmails = [ + 'alice@example.com', + 'bob.smith@linagora.com', + 'user_name@sub.domain.org', + 'user-name@domain.io', + 'user+folder@gmail.com', + '"quoted.name"@example.com', + 'first.last@123domain.net', + ]; + + for (final email in validEmails) { + test('should return true for "$email"', () { + expect(EmailUtils.isValidEmail(email), isTrue); + }); + } + }); + + group('Valid localhost emails', () { + final localhostEmails = [ + 'root@localhost', + 'admin@localhost', + 'test_user@localhost', + 'a.b.c@localhost', + ]; + + for (final email in localhostEmails) { + test('should return true for "$email"', () { + expect(EmailUtils.isValidEmail(email), isTrue); + }); + } + }); + + group('Invalid emails', () { + final invalidEmails = [ + '', // empty + ' ', // whitespace + 'plainaddress', // no @ + '@domain.com', // missing local-part + 'user@', // missing domain + 'user@.', // invalid domain + 'user@domain..com', // double dot + 'user@@domain.com', // double @ + '.user@domain.com', // starts with dot + 'user.@domain.com', // ends with dot + 'user..name@domain.com', // double dot local + 'user@-domain.com', // domain starts with dash + 'user@domain-.com', // domain ends with dash + 'user@domain,com', // invalid character + 'user domain@example.com', // space inside + 'user@domain@domain.com', // multiple @ + 'user@localhost.', // trailing dot + 'user@localhost:8080', // port + 'user@localhost/path', // path + ]; + + for (final email in invalidEmails) { + test('should return false for "$email"', () { + expect(EmailUtils.isValidEmail(email), isFalse); + }); + } + }); + + group('⚙️ Edge cases & error catching', () { + test('should return false for malformed quoted string', () { + expect(EmailUtils.isValidEmail('"ab..cd"@example.com'), isFalse); + }); + + test('should return false for domain with brackets but invalid IP', () { + expect(EmailUtils.isValidEmail('user@[999.999.999.999]'), isFalse); + }); + + test('should return false for missing domain after @', () { + expect(EmailUtils.isValidEmail('user@'), isFalse); + }); + + test('should return false for special chars in local part', () { + expect(EmailUtils.isValidEmail('user()@domain.com'), isFalse); + }); + + test('should trim spaces before validating', () { + expect(EmailUtils.isValidEmail(' alice@example.com '), isTrue); + }); + }); + }); +} diff --git a/test/main/utils/app_utils_test.dart b/test/main/utils/app_utils_test.dart deleted file mode 100644 index 93f71cfbf..000000000 --- a/test/main/utils/app_utils_test.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; -import 'package:model/model.dart'; -import 'package:tmail_ui_user/main/utils/app_utils.dart'; - -void main() { - group('Validation email address test', () { - - final emailsValid = [ - EmailAddress("userName", "alice@localhost"), - EmailAddress("userName", "bob@localhost"), - EmailAddress("userName", "123@localhost"), - EmailAddress("userName", "alic.2312e@localhost"), - EmailAddress("userName", "alice_linagora@localhost") - ]; - - final emailsInvalid = [ - EmailAddress("userName", "localhost"), - EmailAddress("userName", "bob@local"), - EmailAddress("userName", "123@domain"), - EmailAddress("userName", "alic.2312e@"), - EmailAddress("userName", "a,.2312lic.2312e@") - ]; - - test('Valid localhost email addresses', () { - for (final email in emailsValid) { - expect(AppUtils.isEmailLocalhost(email.emailAddress), equals(true)); - } - }); - - test('Invalid localhost email addresses', () { - for (final email in emailsInvalid) { - expect(AppUtils.isEmailLocalhost(email.emailAddress), equals(false)); - } - }); - }); -} \ No newline at end of file diff --git a/test/main/utils/is_email_localhost_test.dart b/test/main/utils/is_email_localhost_test.dart new file mode 100644 index 000000000..5a6e97322 --- /dev/null +++ b/test/main/utils/is_email_localhost_test.dart @@ -0,0 +1,89 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; +import 'package:model/model.dart'; +import 'package:tmail_ui_user/main/utils/app_utils.dart'; + +void main() { + group('AppUtils::isEmailLocalhost', () { + group('Valid localhost email addresses', () { + final validEmails = [ + EmailAddress('userName', 'alice@localhost'), + EmailAddress('userName', 'bob@localhost'), + EmailAddress('userName', '123@localhost'), + EmailAddress('userName', 'alic.2312e@localhost'), + EmailAddress('userName', 'alice_linagora@localhost'), + EmailAddress('userName', 'user-name@localhost'), + EmailAddress('userName', 'USER@localhost'), // uppercase local part + EmailAddress('userName', '"quoted.name"@localhost'), + EmailAddress('userName', 'a.b-c_d@localhost'), + ]; + + for (final email in validEmails) { + test('should return true for "${email.emailAddress}"', () { + expect(AppUtils.isEmailLocalhost(email.emailAddress), isTrue); + }); + } + }); + + group('Invalid localhost email addresses', () { + final invalidEmails = [ + // Missing @localhost + EmailAddress('userName', 'localhost'), + // Wrong or incomplete domain + EmailAddress('userName', 'bob@local'), + EmailAddress('userName', '123@domain'), + EmailAddress('userName', 'alic.2312e@'), + // Invalid characters in local part + EmailAddress('userName', 'a,.2312lic.2312e@'), + EmailAddress('userName', 'ali ce@localhost'), + EmailAddress('userName', 'ali,ce@localhost'), + // Multiple @ symbols + EmailAddress('userName', 'a@b@localhost'), + // Domain variants that look similar but invalid + EmailAddress('userName', 'alice@localhost.com'), + EmailAddress('userName', 'alice@.localhost'), + EmailAddress('userName', 'alice@localhost.'), + // Missing local part + EmailAddress('userName', '@localhost'), + // Empty or only spaces + EmailAddress('userName', ''), + EmailAddress('userName', ' '), + // URL-like or extended domain format + EmailAddress('userName', 'alice@localhost:8080'), + EmailAddress('userName', 'alice@localhost/path'), + ]; + + for (final email in invalidEmails) { + test('should return false for "${email.emailAddress}"', () { + expect(AppUtils.isEmailLocalhost(email.emailAddress), isFalse); + }); + } + }); + + group('Edge cases & normalization', () { + test('should trim spaces and still be valid', () { + expect(AppUtils.isEmailLocalhost(' alice@localhost '), isTrue); + }); + + test('should be case-sensitive for domain', () { + expect(AppUtils.isEmailLocalhost('alice@LOCALHOST'), isFalse); + }); + + test('should allow uppercase local part', () { + expect(AppUtils.isEmailLocalhost('ALICE@localhost'), isTrue); + }); + + test('should reject quoted local part with invalid dots', () { + expect(AppUtils.isEmailLocalhost('"ab..cd"@localhost'), isTrue); + }); + + test('should reject local part with trailing dot', () { + expect(AppUtils.isEmailLocalhost('alice.@localhost'), isFalse); + }); + + test('should reject local part starting with dot', () { + expect(AppUtils.isEmailLocalhost('.alice@localhost'), isFalse); + }); + }); + }); +}