import 'package:core/data/network/dio_client.dart';
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
import 'package:core/presentation/utils/html_transformer/dom/image_transformers.dart';
import 'package:core/presentation/utils/html_transformer/dom/normalize_line_height_in_style_transformer.dart';
import 'package:core/utils/html/html_utils.dart';
import 'package:core/utils/string_convert.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:html/parser.dart' as html_parser;
import 'package:mockito/mockito.dart';

class MockDioClient extends Mock implements DioClient {}

/// Tests to verify that ReDoS (Regular Expression Denial of Service) vulnerabilities
/// have been properly patched. These tests use inputs that would cause catastrophic
/// backtracking in the old regex patterns.
void main() {
  group('ReDoS Vulnerability Patches -', () {
    group('DomTransformer._backgroundImageRegex', () {
      const transformer = ImageTransformer();

      test('should handle very long background-image style without freezing', () {
        // Create a malicious input that would cause catastrophic backtracking
        // in the old pattern: r'background-image:\s*url\(([^)]+)\).*?'
        final maliciousStyle = 'background-image: url(${'a' * 10000})';

        final stopwatch = Stopwatch()..start();
        final result = transformer.findImageUrlFromStyleTag(maliciousStyle);
        stopwatch.stop();

        // Should complete within a reasonable time (< 100ms)
        expect(stopwatch.elapsedMilliseconds, lessThan(100),
            reason: 'Regex should not cause catastrophic backtracking');
        expect(result, isNotNull);
      });

      test('should handle style with many nested parentheses', () {
        // Primary purpose: Verify that processing malformed input with many nested
        // parentheses doesn't cause catastrophic backtracking (ReDoS vulnerability).
        // This pattern would freeze the old unbounded regex.
        //
        // Note: The current implementation is permissive - it extracts the URL content
        // even with malformed nesting. This is acceptable for a parser that prioritizes
        // resilience over strict validation.
        final style = 'background-image: url(${'(' * 1000}example.jpg${')' * 1000})';

        final stopwatch = Stopwatch()..start();
        final result = transformer.findImageUrlFromStyleTag(style);
        stopwatch.stop();

        // Performance: Should complete quickly without catastrophic backtracking
        expect(stopwatch.elapsedMilliseconds, lessThan(100),
            reason: 'Should process quickly despite nested parentheses');

        // Functional: Current implementation extracts URL despite malformed nesting
        expect(result, isNotNull,
            reason: 'Parser is permissive and extracts URL from malformed input');
      });

      test('should reject malformed background-image patterns quickly', () {
        // Input with many whitespace and incomplete patterns
        final maliciousInput = 'background-image:${' ' * 10000}url(';

        final stopwatch = Stopwatch()..start();
        final result = transformer.findImageUrlFromStyleTag(maliciousInput);
        stopwatch.stop();

        expect(stopwatch.elapsedMilliseconds, lessThan(100));
        expect(result, isNull);
      });

      test('should correctly parse valid background-image patterns', () {
        const validPatterns = [
          'background-image: url(example.com/image.jpg)',
          'background-image: url("example.com/image.jpg")',
          "background-image: url('example.com/image.jpg')",
          'background-image:url(example.com/image.jpg)',
        ];

        for (final pattern in validPatterns) {
          final result = transformer.findImageUrlFromStyleTag(pattern);
          expect(result, isNotNull, reason: 'Should parse: $pattern');
          expect(result!.value2, contains('example.com/image.jpg'));
        }
      });
    });

    group('NormalizeLineHeightInStyleTransformer', () {
      late NormalizeLineHeightInStyleTransformer transformer;
      late MockDioClient mockDioClient;

      setUp(() {
        transformer = const NormalizeLineHeightInStyleTransformer();
        mockDioClient = MockDioClient();
      });

      test('should handle very long style strings efficiently', () async {
        // Create HTML with very long style attribute
        final longStyle = 'color: red; ' * 10000 + 'line-height: 1px;';
        final htmlContent = '<div style="$longStyle">Content</div>';
        final document = html_parser.parse(htmlContent);

        final stopwatch = Stopwatch()..start();
        await transformer.process(
          document: document,
          dioClient: mockDioClient,
        );
        stopwatch.stop();

        expect(stopwatch.elapsedMilliseconds, lessThan(100));

        // Verify the transformer removed line-height
        final divElement = document.querySelector('div');
        final resultStyle = divElement?.attributes['style'] ?? '';
        expect(resultStyle, isNot(contains('line-height')));
      });

      test('should handle many consecutive spaces efficiently', () async {
        // Create HTML with many spaces in style that includes line-height
        final manySpaces = 'color:${' ' * 10000}red; line-height: 1px;';
        final htmlContent = '<p style="$manySpaces">Text</p>';
        final document = html_parser.parse(htmlContent);

        final stopwatch = Stopwatch()..start();
        await transformer.process(
          document: document,
          dioClient: mockDioClient,
        );
        stopwatch.stop();

        expect(stopwatch.elapsedMilliseconds, lessThan(100));

        // After removing line-height, spaces should be normalized
        final pElement = document.querySelector('p');
        final resultStyle = pElement?.attributes['style'] ?? '';
        expect(resultStyle, isNot(contains('line-height')));
        // Multiple consecutive spaces should be normalized to single space
        expect(resultStyle, isNot(matches(r' {2,}')));
      });

      test('should remove line-height: 1px and 100% patterns', () async {
        final testCases = [
          '<div style="line-height: 1px; color: blue;">Test 1px</div>',
          '<div style="line-height: 100%; color: red;">Test 100%</div>',
          '<div style="line-height:1px">No spaces</div>',
          '<div style="LINE-HEIGHT: 1PX;">Case insensitive</div>',
        ];

        for (final htmlContent in testCases) {
          final document = html_parser.parse(htmlContent);
          await transformer.process(
            document: document,
            dioClient: mockDioClient,
          );

          final divElement = document.querySelector('div');
          final resultStyle = divElement?.attributes['style'] ?? '';
          expect(resultStyle, isNot(contains('line-height')),
              reason: 'Should remove line-height from: $htmlContent');
        }
      });

      test('should preserve other line-height values', () async {
        final testCases = [
          '<div style="line-height: 1.5; color: blue;">Test 1.5</div>',
          '<div style="line-height: 20px; color: red;">Test 20px</div>',
          '<div style="line-height: normal;">Test normal</div>',
        ];

        for (final htmlContent in testCases) {
          final document = html_parser.parse(htmlContent);
          await transformer.process(
            document: document,
            dioClient: mockDioClient,
          );

          final divElement = document.querySelector('div');
          final resultStyle = divElement?.attributes['style'] ?? '';
          expect(resultStyle, contains('line-height'),
              reason: 'Should preserve line-height from: $htmlContent');
        }
      });

      test('should remove style attribute when empty after transformation', () async {
        const htmlContent = '<div style="line-height: 1px;">Empty after</div>';
        final document = html_parser.parse(htmlContent);

        await transformer.process(
          document: document,
          dioClient: mockDioClient,
        );

        final divElement = document.querySelector('div');
        expect(divElement?.attributes.containsKey('style'), isFalse,
            reason: 'Should remove empty style attribute');
      });

      test('should normalize multiple spaces after removing line-height', () async {
        const htmlContent = '<div style="color: red;   line-height: 1px;   font-size: 12px;">Multi-space</div>';
        final document = html_parser.parse(htmlContent);

        await transformer.process(
          document: document,
          dioClient: mockDioClient,
        );

        final divElement = document.querySelector('div');
        final resultStyle = divElement?.attributes['style'] ?? '';
        expect(resultStyle, isNot(contains('  '))); // No double spaces
        expect(resultStyle, contains('color: red'));
        expect(resultStyle, contains('font-size: 12px'));
      });
    });

    group('HtmlUtils regex patterns', () {
      test('_htmlStartTagRegex should handle malicious tag patterns', () {
        // Old pattern: r'<[a-zA-Z][^>]*>' could cause issues with many attributes
        final maliciousTag = '<div${'a' * 10000}>';

        final stopwatch = Stopwatch()..start();
        HtmlUtils.addQuoteToggle(maliciousTag);
        stopwatch.stop();

        expect(stopwatch.elapsedMilliseconds, lessThan(100));
      });

      test('_htmlEndTagRegex should have bounded quantifier', () {
        // Old pattern had unbounded quantifier: r'</[a-zA-Z][^>]*>'
        // New pattern has max 128 chars: r'</[a-zA-Z][^>]{0,128}>'
        final longEndTag = '</div${'x' * 200}>';

        final stopwatch = Stopwatch()..start();
        HtmlUtils.addQuoteToggle(longEndTag);
        stopwatch.stop();

        expect(stopwatch.elapsedMilliseconds, lessThan(100));
      });

      test('_urlRegex should handle long URLs efficiently', () {
        // Old pattern: r'(?:(?:https?:\/\/)|...)(?!\.)(?!.*\.\.)([^\s<]+[^<.,:;\"\'\)\]\s!?])'
        // had potential for catastrophic backtracking
        final longUrl = 'https://${'a' * 10000}.com${'b' * 10000}';

        final stopwatch = Stopwatch()..start();
        HtmlUtils.wrapPlainTextLinks('<p>$longUrl</p>');
        stopwatch.stop();

        expect(stopwatch.elapsedMilliseconds, lessThan(200));
      });

      test('_urlRegex should correctly match valid URLs', () {
        const validUrls = [
          'https://example.com',
          'http://test.org',
          'ftp://files.com',
          'mailto:test@example.com',
          'file:///path/to/file',
          'www.example.com',
        ];

        for (final url in validUrls) {
          final html = '<p>Visit $url today</p>';
          final result = HtmlUtils.wrapPlainTextLinks(html);
          expect(result, contains('<a href='), reason: 'Should wrap: $url');
        }
      });

      test('_urlRegex should reject malformed URLs quickly', () {
        const malformedUrls = [
          'http://.example.com',  // Leading dot
          'https://..example.com',  // Double dots
          'www..com',  // Double dots
        ];

        for (final url in malformedUrls) {
          final html = '<p>Visit $url today</p>';
          final stopwatch = Stopwatch()..start();
          HtmlUtils.wrapPlainTextLinks(html);
          stopwatch.stop();

          expect(stopwatch.elapsedMilliseconds, lessThan(100),
              reason: 'Should process quickly: $url');
        }
      });

      test('extractPlainText should handle long HTML with escaped tags', () {
        // Test that escaped tag processing doesn't cause issues
        final longHtml = '${'&lt;div&gt;' * 1000}content${'&lt;/div&gt;' * 1000}';

        final stopwatch = Stopwatch()..start();
        final result = HtmlUtils.extractPlainText(longHtml);
        stopwatch.stop();

        expect(stopwatch.elapsedMilliseconds, lessThan(500));
        expect(result, contains('content'));
      });

      test('Should handle Gmail newsletter HTML structure', () {
        // Real pattern from Gmail promotional emails
        const gmailHtml = '''
        <div dir="ltr">
        <table cellpadding="0" cellspacing="0" border="0" width="100%">
            <tr>
            <td style="background-image: url('https://ci3.googleusercontent.com/proxy/ABCDEFGHIJKLMNOP1234567890abcdefghijklmnop=s0-d-e1-ft#https://example.com/images/header.jpg'); background-size: cover;">
                <p style="font-family: Arial, sans-serif;">Hello User,</p>
                <p>Visit https://example.com/products for our latest offers.</p>
            </td>
            </tr>
        </table>
        </div>
    ''';

        final stopwatch = Stopwatch()..start();

        // Extract plain text
        final plainText = HtmlUtils.extractPlainText(gmailHtml);

        // Wrap URLs
        final linkedHtml = HtmlUtils.wrapPlainTextLinks(gmailHtml);

        stopwatch.stop();

        // Performance check
        expect(stopwatch.elapsedMilliseconds, lessThan(200),
            reason: 'Gmail HTML processing should be fast');

        // Functional check
        expect(plainText, contains('Hello User'));
        expect(plainText, contains('Visit'));
        expect(linkedHtml, contains('<a href='));
      });

      test('Should handle URLs with complex query parameters', () {
        const complexUrls = [
          // Tracking parameters
          'https://example.com/article?utm_source=email&utm_medium=newsletter&utm_campaign=spring2024',
          // Nested URL in parameter
          'https://redirect.example.com?url=https://target.com/page&ref=email',
          // Fragment identifier
          'https://docs.example.com/guide#installation-steps',
          // Multiple fragments (invalid but should handle)
          'https://example.com/page#section1#subsection',
          // Query + Fragment
          'https://api.example.com/v1/users?id=123&format=json#results',
          // Encoded characters
          'https://example.com/search?q=hello%20world&lang=en',
        ];

        for (final url in complexUrls) {
          final html = '<p>Visit $url for details</p>';

          final stopwatch = Stopwatch()..start();
          final result = HtmlUtils.wrapPlainTextLinks(html);
          stopwatch.stop();

          // Performance check
          expect(stopwatch.elapsedMilliseconds, lessThan(100),
              reason: 'Should process quickly: $url');

          // Functional check
          expect(result, contains('<a href='),
              reason: 'Should wrap URL: $url');
          expect(result, contains(url.split('?')[0]),
              reason: 'Should preserve URL base: $url');
        }
      });

      test('Should handle HTML tags at 128 character boundary', () {
        final exactly128 = '</div${'x' * 122}>';

        // 129 characters (over limit, should still process fast)
        final over128 = '</div${'x' * 123}>';

        // Very long (1000 chars - malicious)
        final malicious = '</div${'x' * 995}>';

        final stopwatch1 = Stopwatch()..start();
        HtmlUtils.addQuoteToggle('Text $exactly128 end');
        stopwatch1.stop();

        final stopwatch2 = Stopwatch()..start();
        HtmlUtils.addQuoteToggle('Text $over128 end');
        stopwatch2.stop();

        final stopwatch3 = Stopwatch()..start();
        HtmlUtils.addQuoteToggle('Text $malicious end');
        stopwatch3.stop();

        // All should complete quickly (bounded quantifier prevents backtracking)
        expect(stopwatch1.elapsedMilliseconds, lessThan(100),
            reason: '128 chars (boundary) should be fast');
        expect(stopwatch2.elapsedMilliseconds, lessThan(100),
            reason: '129 chars (over boundary) should be fast');
        expect(stopwatch3.elapsedMilliseconds, lessThan(100),
            reason: '1000 chars (malicious) should be fast');
      });

      test('Should handle deeply nested HTML structure', () {
        // 50 levels (realistic maximum)
        final deepNesting = '${'<div>' * 50}<p>Deep content with https://example.com link</p>${'</div>' * 50}';

        // 100 levels (extreme)
        final extremeNesting = '${'<span>' * 100}Content${'</span>' * 100}';

        final stopwatch1 = Stopwatch()..start();
        final plainText1 = HtmlUtils.extractPlainText(deepNesting);
        final linked1 = HtmlUtils.wrapPlainTextLinks(deepNesting);
        stopwatch1.stop();

        final stopwatch2 = Stopwatch()..start();
        final plainText2 = HtmlUtils.extractPlainText(extremeNesting);
        final linked2 = HtmlUtils.wrapPlainTextLinks(extremeNesting);
        stopwatch2.stop();

        // Performance checks
        expect(stopwatch1.elapsedMilliseconds, lessThan(200),
            reason: '50 levels of nesting should process quickly');
        expect(stopwatch2.elapsedMilliseconds, lessThan(300),
            reason: '100 levels of nesting should process quickly');

        // Functional checks
        expect(plainText1, contains('Deep content'));
        expect(linked1, contains('<a href='));
        expect(plainText2, contains('Content'));

        // Verify extreme nesting doesn't break link wrapping
        expect(linked2, isNotNull,
            reason: 'Link wrapping should handle extreme nesting without crashing');
      });
    });

    group('StringConvert regex patterns', () {
      test('_base64ValidationRegex should handle long invalid base64', () {
        // Test with a very long string that looks like base64 but isn't
        final longInvalidBase64 = 'A' * 10000 + '!'; // Not valid base64

        final stopwatch = Stopwatch()..start();
        StringConvert.extractEmailAddress(longInvalidBase64);
        stopwatch.stop();

        expect(stopwatch.elapsedMilliseconds, lessThan(100));
      });

      test('_mdSeparatorRegex should handle malicious markdown separators', () {
        // Old pattern: r'^\|?(\s*:?-+:?\s*\|)+(\s*:?-+:?\s*)\|?$'
        // Could cause issues with many repeating patterns
        final maliciousMarkdown = '|${':---:' * 1000}|';

        final stopwatch = Stopwatch()..start();
        StringConvert.isTextTable('$maliciousMarkdown\n$maliciousMarkdown');
        stopwatch.stop();

        expect(stopwatch.elapsedMilliseconds, lessThan(100));
      });

      test('_asciiArtRegex should efficiently check long text', () {
        final longText = 'a' * 10000 + '+';

        final stopwatch = Stopwatch()..start();
        StringConvert.isTextTable('$longText\n$longText');
        stopwatch.stop();

        expect(stopwatch.elapsedMilliseconds, lessThan(100));
      });

      test('_namedAddressRegex should handle many quoted addresses', () {
        // Test with many named addresses
        final manyAddresses = List.generate(
          1000,
          (i) => '"User $i" <user$i@example.com>',
        ).join(', ');

        final stopwatch = Stopwatch()..start();
        final result = StringConvert.extractNamedAddresses(manyAddresses);
        stopwatch.stop();

        expect(stopwatch.elapsedMilliseconds, lessThan(500));
        expect(result.length, equals(1000));
      });

      test('extractEmailAddress should handle malicious separator patterns', () {
        // Old pattern could have issues with many mixed separators
        final maliciousInput = 'email@test.com${', ; ' * 10000}another@test.com';

        final stopwatch = Stopwatch()..start();
        final result = StringConvert.extractEmailAddress(maliciousInput);
        stopwatch.stop();

        expect(stopwatch.elapsedMilliseconds, lessThan(100));
        expect(result, contains('email@test.com'));
        expect(result, contains('another@test.com'));
      });

      test('Should handle emails with special characters - valid cases', () {
        // Valid emails that should be extracted successfully
        final validEmailInputs = <String, String>{
          'user+tag@example.com': 'user+tag@example.com',
          'user.name@example.com': 'user.name@example.com',
          'user_name@example.com': 'user_name@example.com',
          'user-name@example.com': 'user-name@example.com',
          'user123@example.com': 'user123@example.com',
          '123user@example.com': '123user@example.com',
          'user@example.co.uk': 'user@example.co.uk',
          'user@sub.domain.example.com': 'user@sub.domain.example.com',
          'a.b.c.d@example.com': 'a.b.c.d@example.com',
          'user@例え.jp': 'user@例え.jp',
          '${'a' * 64}@example.com': '${'a' * 64}@example.com',
          'Contact us at support@example.com for help': 'support@example.com',
          'Multiple emails: alice@test.com, bob@test.org': 'alice@test.com',
        };

        for (final entry in validEmailInputs.entries) {
          final input = entry.key;
          final expectedEmail = entry.value;

          final stopwatch = Stopwatch()..start();
          final result = StringConvert.extractEmailAddress(input);
          stopwatch.stop();

          expect(stopwatch.elapsedMilliseconds, lessThan(100),
              reason: 'Should process quickly: $input');

          expect(result, contains(expectedEmail),
              reason: 'Should extract "$expectedEmail" from: $input');
        }
      });

      test('Should handle quoted email addresses', () {
        // Quoted email addresses have special handling in email specs
        const quotedEmails = [
          '"user@name"@example.com',
          '"user name"@example.com',
        ];

        for (final input in quotedEmails) {
          final stopwatch = Stopwatch()..start();
          final result = StringConvert.extractEmailAddress(input);
          stopwatch.stop();

          // Performance check
          expect(stopwatch.elapsedMilliseconds, lessThan(100),
              reason: 'Should process quickly: $input');

          // Should extract something (even if split) - no crash
          expect(result, isA<List<String>>(),
              reason: 'Should return a list for: $input');
          expect(result, isNotEmpty,
              reason: 'Should extract something from: $input');
        }
      });

      test('Should gracefully handle invalid or edge case emails', () {
        // Invalid emails or edge cases - should handle gracefully without crashing
        const edgeCaseInputs = [
          'user.@example.com',  // Dot before @
          '.user@example.com',  // Leading dot
          'user..name@example.com',  // Consecutive dots
          '@example.com',  // Missing local part
          'user@',  // Missing domain
          'user',  // No @ symbol
          '',  // Empty string
          '   ',  // Only whitespace
        ];

        for (final input in edgeCaseInputs) {
          final stopwatch = Stopwatch()..start();
          final result = StringConvert.extractEmailAddress(input);
          stopwatch.stop();

          // Performance check - should handle gracefully without hanging
          expect(stopwatch.elapsedMilliseconds, lessThan(100),
              reason: 'Should process quickly even for invalid: $input');

          // Result should be a list (even if empty) - no crash
          expect(result, isA<List<String>>(),
              reason: 'Should return a list for: $input');
        }
      });

      test('isTextTable should correctly identify valid tables', () {
        const validTables = [
          '''
| Header 1 | Header 2 |
|----------|----------|
| Cell 1   | Cell 2   |
''',
          '''
+--------+--------+
| Cell 1 | Cell 2 |
+--------+--------+
''',
        ];

        for (final table in validTables) {
          expect(StringConvert.isTextTable(table), isTrue,
              reason: 'Should identify as table');
        }
      });

      test('isTextTable should correctly reject non-tables', () {
        const nonTables = [
          'Just plain text',
          'Single line with | pipes',
          '',
          '   \n   ',
        ];

        for (final text in nonTables) {
          expect(StringConvert.isTextTable(text), isFalse,
              reason: 'Should not identify as table: $text');
        }
      });
    });

    group('StringConvert.isEmailLocalhost', () {
      test('should handle very long email addresses efficiently', () {
        final longEmail = '${'a' * 10000}@localhost';

        final stopwatch = Stopwatch()..start();
        final result = StringConvert.isEmailLocalhost(longEmail);
        stopwatch.stop();

        expect(stopwatch.elapsedMilliseconds, lessThan(100));
        expect(result, isTrue);
      });

      test('should correctly match valid localhost emails', () {
        const validEmails = [
          'user@localhost',
          'user.name@localhost',
          '"User Name"@localhost',
        ];

        for (final email in validEmails) {
          expect(StringConvert.isEmailLocalhost(email), isTrue,
              reason: 'Should match: $email');
        }
      });

      test('should reject invalid localhost emails', () {
        const invalidEmails = [
          'user@localhost.com',
          '@localhost',
          'user@',
          'not-an-email',
        ];

        for (final email in invalidEmails) {
          expect(StringConvert.isEmailLocalhost(email), isFalse,
              reason: 'Should not match: $email');
        }
      });
    });

    group('Performance stress tests', () {
      test('all regex patterns should handle edge case inputs within time limit', () {
        // Create various edge case inputs
        final edgeCases = [
          'x' * 100000,  // Very long string
          '(' * 1000 + ')' * 1000,  // Many nested parentheses
          ' ' * 100000,  // Many spaces
          '\n' * 10000,  // Many newlines
          '|||||||' * 10000,  // Many pipes
          '-------' * 10000,  // Many dashes
        ];

        for (final input in edgeCases) {
          final stopwatch = Stopwatch()..start();

          // Test various functions with edge case input
          try {
            StringConvert.extractEmailAddress(input);
            StringConvert.isTextTable('$input\n$input');
            HtmlUtils.extractPlainText(input);
          } catch (_) {
            // Intentionally ignored - focus is on performance, not correctness
          }

          stopwatch.stop();

          expect(stopwatch.elapsedMilliseconds, lessThan(1000),
              reason: 'Should process edge case within 1 second');
        }
      });
    });

    group('Regex initialization optimization', () {
      test('regex patterns should be reused, not recreated', () {
        // This test verifies that all refactored regex patterns are static and reused
        // across multiple calls, preventing unnecessary regex compilation overhead

        // StringConvert patterns
        final base64_1 = StringConvert.base64ValidationRegex;
        final base64_2 = StringConvert.base64ValidationRegex;
        expect(identical(base64_1, base64_2), true,
            reason: 'base64ValidationRegex should be static and reused');

        final emailLocalhost1 = StringConvert.emailLocalhostRegex;
        final emailLocalhost2 = StringConvert.emailLocalhostRegex;
        expect(identical(emailLocalhost1, emailLocalhost2), true,
            reason: 'emailLocalhostRegex should be static and reused');

        // DomTransformer patterns
        final bgImage1 = DomTransformer.backgroundImageRegex;
        final bgImage2 = DomTransformer.backgroundImageRegex;
        expect(identical(bgImage1, bgImage2), true,
            reason: 'backgroundImageRegex should be static and reused');

        // NormalizeLineHeightInStyleTransformer patterns
        final lineHeight1 = NormalizeLineHeightInStyleTransformer.lineHeightPattern;
        final lineHeight2 = NormalizeLineHeightInStyleTransformer.lineHeightPattern;
        expect(identical(lineHeight1, lineHeight2), true,
            reason: 'lineHeightPattern should be static and reused');

        // HtmlUtils patterns
        final htmlStartTag1 = HtmlUtils.htmlStartTagRegex;
        final htmlStartTag2 = HtmlUtils.htmlStartTagRegex;
        expect(identical(htmlStartTag1, htmlStartTag2), true,
            reason: 'htmlStartTagRegex should be static and reused');

        final htmlEndTag1 = HtmlUtils.htmlEndTagRegex;
        final htmlEndTag2 = HtmlUtils.htmlEndTagRegex;
        expect(identical(htmlEndTag1, htmlEndTag2), true,
            reason: 'htmlEndTagRegex should be static and reused');

        final urlRegex1 = HtmlUtils.urlRegex;
        final urlRegex2 = HtmlUtils.urlRegex;
        expect(identical(urlRegex1, urlRegex2), true,
            reason: 'urlRegex should be static and reused');
      });
    });
  });
}
