// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
import 'package:flutter_test/flutter_test.dart';

import 'utils.dart';

void main() => defineTests();

void defineTests() {
  group('Link', () {
    testWidgets(
      'should work with nested elements',
      (WidgetTester tester) async {
        final List<MarkdownLink> linkTapResults = <MarkdownLink>[];
        const String data = '[Link `with nested code` Text](href)';
        await tester.pumpWidget(
          boilerplate(
            Markdown(
              data: data,
              onTapLink: (String text, String? href, String title) =>
                  linkTapResults.add(MarkdownLink(text, href, title)),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;

        final List<Type> gestureRecognizerTypes = <Type>[];
        span.visitChildren((InlineSpan inlineSpan) {
          if (inlineSpan is TextSpan) {
            final TapGestureRecognizer? recognizer = inlineSpan.recognizer as TapGestureRecognizer?;
            gestureRecognizerTypes.add(recognizer?.runtimeType ?? Null);
            if (recognizer != null) {
              recognizer.onTap!();
            }
          }
          return true;
        });

        expect(span.children!.length, 3);
        expect(gestureRecognizerTypes.length, 3);
        expect(gestureRecognizerTypes, everyElement(TapGestureRecognizer));
        expect(linkTapResults.length, 3);

        // Each of the child text span runs should return the same link info.
        for (final MarkdownLink tapResult in linkTapResults) {
          expectLinkTap(tapResult, const MarkdownLink('Link with nested code Text', 'href'));
        }
      },
    );

    testWidgets(
      'should work next to other links',
      (WidgetTester tester) async {
        final List<MarkdownLink> linkTapResults = <MarkdownLink>[];
        const String data = '[First Link](firstHref) and [Second Link](secondHref)';
        await tester.pumpWidget(
          boilerplate(
            Markdown(
              data: data,
              onTapLink: (String text, String? href, String title) =>
                  linkTapResults.add(MarkdownLink(text, href, title)),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;

        final List<Type> gestureRecognizerTypes = <Type>[];
        span.visitChildren((InlineSpan inlineSpan) {
          if (inlineSpan is TextSpan) {
            final TapGestureRecognizer? recognizer = inlineSpan.recognizer as TapGestureRecognizer?;
            gestureRecognizerTypes.add(recognizer?.runtimeType ?? Null);
            if (recognizer != null) {
              recognizer.onTap!();
            }
          }
          return true;
        });

        expect(span.children!.length, 3);
        expect(
          gestureRecognizerTypes,
          orderedEquals(<Type>[TapGestureRecognizer, Null, TapGestureRecognizer]),
        );
        expectLinkTap(linkTapResults[0], const MarkdownLink('First Link', 'firstHref'));
        expectLinkTap(linkTapResults[1], const MarkdownLink('Second Link', 'secondHref'));
      },
    );

    testWidgets(
      'multiple inline links with same name but different urls - unique keys are assigned automatically',
      (WidgetTester tester) async {
        //Arange
        final Widget toBePumped = boilerplate(
          Column(
            children: <Widget>[
              MarkdownBody(
                data: '[link](link1.com)',
                onTapLink: (String text, String? href, String title) {},
              ),
              MarkdownBody(
                data: '[link](link2.com)',
                onTapLink: (String text, String? href, String title) {},
              ),
            ],
          ),
        );

        //Act
        await tester.pumpWidget(toBePumped);

        //Assert
        final Finder widgetFinder = find.byType(Text);
        final List<Element> elements = widgetFinder.evaluate().toList();
        final List<Widget> widgets = elements.map((Element e) => e.widget).toList();

        final List<String> keys = widgets
            .where((Widget w) => w.key != null && w.key.toString().isNotEmpty)
            .map((Widget w) => w.key.toString())
            .toList();
        expect(keys.length, 2); //Not empty
        expect(keys.toSet().length, 2); // Unique
      },
    );
    testWidgets(
      'multiple inline links with same content should not throw an exception',
      (WidgetTester tester) async {
        //Arrange
        final Widget toBePumped = boilerplate(
          Column(
            children: <Widget>[
              Expanded(
                child: MarkdownBody(
                  data:
                      '''links: [![first](image.png)](https://link.com) [![second](image.png)](https://link.com) [![third](image.png)](https://link.com)''',
                  onTapLink: (String text, String? href, String title) {},
                ),
              ),
            ],
          ),
        );

        //Act
        await tester.pumpWidget(toBePumped);

        //Assert
        final Finder widgetFinder = find.byType(Text, skipOffstage: false);
        final List<Element> elements = widgetFinder.evaluate().toList();
        final List<Widget> widgets = elements.map((Element e) => e.widget).toList();

        final List<String> keys = widgets
            .where((Widget w) => w.key != null && w.key.toString().isNotEmpty)
            .map((Widget w) => w.key.toString())
            .toList();
        expect(keys.length, 3); //all three links.
        expect(keys.toSet().length, 3); // all three unique.
      },
    );

    testWidgets(
      // Example 493 from GFM.
      'simple inline link',
      (WidgetTester tester) async {
        const String data = '[link](/uri "title")';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', '/uri', 'title'));
      },
    );

    testWidgets(
      'empty inline link',
      (WidgetTester tester) async {
        const String data = '[](/uri "title")';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expect(find.byType(RichText), findsNothing);
        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 494 from GFM.
      'simple inline link - title omitted',
      (WidgetTester tester) async {
        const String data = '[link](/uri)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', '/uri'));
      },
    );

    testWidgets(
      // Example 495 from GFM.
      'simple inline link - both destination and title omitted',
      (WidgetTester tester) async {
        const String data = '[link]()';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', ''));
      },
    );

    testWidgets(
      // Example 496 from GFM.
      'simple inline link - both < > enclosed destination and title omitted',
      (WidgetTester tester) async {
        const String data = '[link](<>)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', ''));
      },
    );

    testWidgets(
      // Example 497 from GFM.
      'link destination with space and not < > enclosed',
      (WidgetTester tester) async {
        const String data = '[link](/my url)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        // Link is treated as ordinary text.
        expectInvalidLink('[link](/my url)');
        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 498 from GFM.
      'link destination with space and < > enclosed',
      (WidgetTester tester) async {
        const String data = '[link](</my url>)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', '/my%20url'));
      },
    );

    testWidgets(
      // Example 499 from GFM.
      'link destination cannot contain line breaks - not < > enclosed',
      (WidgetTester tester) async {
        const String data = '[link](foo\nbar)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        // Link is treated as ordinary text.
        expectInvalidLink('[link](foo bar)');
        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 500 from GFM.
      'link destination cannot contain line breaks - < > enclosed',
      (WidgetTester tester) async {
        const String data = '[link](<foo\nbar>)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        // Link is treated as ordinary text.
        expectInvalidLink('[link](<foo bar>)');
        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 501 from GFM.
      'link destination containing ")" and < > enclosed',
      (WidgetTester tester) async {
        const String data = '[link](</my)url>)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', '/my)url'));
      },
    );

    testWidgets(
      // Example 502 from GFM.
      'pointy brackets that enclose links must be unescaped',
      (WidgetTester tester) async {
        const String data = r'[link](<foo\>)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        // Link is treated as ordinary text.
        expectInvalidLink('[link](<foo>)');
        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 503 from GFM.
      'opening pointy brackets are not properly matched',
      (WidgetTester tester) async {
        const String data = '[link](<foo)bar\n[link](<foo)bar>\n[link](<foo>bar)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        // Link is treated as ordinary text.
        expectInvalidLink('[link](<foo)bar [link](<foo)bar> [link](<foo>bar)');
        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 504 from GFM.
      'parentheses inside link destination may be escaped',
      (WidgetTester tester) async {
        const String data = r'[link](\(foo\))';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', '(foo)'));
      },
    );

    testWidgets(
      // Example 505 from GFM.
      'multiple balanced parentheses are allowed without escaping',
      (WidgetTester tester) async {
        const String data = '[link](foo(and(bar)))';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', 'foo(and(bar))'));
      },
    );

    testWidgets(
      // Example 506 from GFM.
      'escaped unbalanced parentheses',
      (WidgetTester tester) async {
        // Use raw string so backslash isn't treated as an escape character.
        const String data = r'[link](foo\(and\(bar\))';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', 'foo(and(bar)'));
      },
    );

    testWidgets(
      // Example 507 from GFM.
      'pointy brackets enclosed unbalanced parentheses',
      (WidgetTester tester) async {
        const String data = '[link](<foo(and(bar)>)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', 'foo(and(bar)'));
      },
    );

    testWidgets(
      // Example 508 from GFM.
      'parentheses and other symbols can be escaped',
      (WidgetTester tester) async {
        // Use raw string so backslash isn't treated as an escape character.
        const String data = r'[link](foo\)\:)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', 'foo):'));
      },
    );

    testWidgets(
      // Example 509 case 1 from GFM.
      'link destinations with just fragment identifier',
      (WidgetTester tester) async {
        const String data = '[link](#fragment)';

        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', '#fragment'));
      },
    );

    testWidgets(
      // Example 509 case 2 from GFM.
      'link destinations with URL and fragment identifier',
      (WidgetTester tester) async {
        const String data = '[link](http://example.com#fragment)';

        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', 'http://example.com#fragment'));
      },
    );

    testWidgets(
      // Example 509 case 3 from GFM.
      'link destinations with URL, fragment identifier, and query',
      (WidgetTester tester) async {
        const String data = '[link](http://example.com?foo=3#fragment)';

        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', 'http://example.com?foo=3#fragment'));
      },
    );

    testWidgets(
      // Example 510 from GFM.
      'link destinations with backslash before non-escapable character',
      (WidgetTester tester) async {
        const String data = '[link](foo\bar)';

        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', 'foo%08ar'));
      },
    );

    testWidgets(
      // Example 511 from GFM.
      'URL escaping should be left alone inside link destination',
      (WidgetTester tester) async {
        const String data = '[link](foo%20b&auml;)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', 'foo%20b%C3%A4'));
      },
    );

    testWidgets(
      // Example 512 from GFM.
      'omitting link destination uses title for destination',
      (WidgetTester tester) async {
        const String data = '[link]("title")';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', '%22title%22'));
      },
    );

    testWidgets(
      // Example 513a from GFM.
      'link title in double quotes',
      (WidgetTester tester) async {
        const String data = '[link](/url "title")';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', '/url', 'title'));
      },
    );

    testWidgets(
      // Example 513b from GFM.
      'link title in single quotes',
      (WidgetTester tester) async {
        const String data = "[link](/url 'title')";
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', '/url', 'title'));
      },
    );

    testWidgets(
      // Example 513c from GFM.
      'link title in parentheses',
      (WidgetTester tester) async {
        const String data = '[link](/url (title))';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', '/url', 'title'));
      },
    );

    testWidgets(
      // Example 514 from GFM.
      'backslash escapes, entity, and numeric character references are allowed in title',
      (WidgetTester tester) async {
        const String data = r'[link](/url "title \"&quot;")';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', '/url', 'title &quot;&quot;'));
      },
    );

    testWidgets(
      // Example 515 from GFM.
      'link title must be separated with whitespace and not Unicode whitespace',
      (WidgetTester tester) async {
        const String data = '[link](/url\u{C2A0}"title")';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', '/url%EC%8A%A0%22title%22'));
      },
    );

    testWidgets(
      // Example 516 from GFM.
      'nested balanced quotes are not allowed without escaping',
      (WidgetTester tester) async {
        const String data = '[link](/url "title "and" title")';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        // Link is treated as ordinary text.
        expectInvalidLink('[link](/url "title "and" title")');
        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 517 from GFM.
      'nested balanced quotes using different quote type',
      (WidgetTester tester) async {
        const String data = '[link](/url \'title "and" title\')';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(
          linkTapResults,
          const MarkdownLink('link', '/url', 'title &quot;and&quot; title'),
        );
      },
    );

    testWidgets(
      // Example 518 from GFM.
      'whitespace is allowed around the destination and title',
      (WidgetTester tester) async {
        const String data = '[link](   /url  "title")';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link');
        expectLinkTap(linkTapResults, const MarkdownLink('link', '/url', 'title'));
      },
    );

    testWidgets(
      // Example 519 from GFM.
      'whitespace is not allowed between link text and following parentheses',
      (WidgetTester tester) async {
        const String data = '[link] (/url)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        // Link is treated as ordinary text.
        expectInvalidLink('[link] (/url)');
        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 520 from GFM.
      'link text may contain balanced brackets',
      (WidgetTester tester) async {
        const String data = '[link [foo [bar]]](/uri)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link [foo [bar]]');
        expectLinkTap(linkTapResults, const MarkdownLink('link [foo [bar]]', '/uri'));
      },
    );

    testWidgets(
      // Example 521 from GFM.
      'link text may not contain unbalanced brackets',
      (WidgetTester tester) async {
        const String data = '[link] bar](/uri)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        // Link is treated as ordinary text.
        expectInvalidLink('[link] bar](/uri)');
        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 522 from GFM.
      'link text may not contain unbalanced brackets - unintended link text',
      (WidgetTester tester) async {
        const String data = '[link [bar](/uri)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);
        expect(span.children![0], isA<TextSpan>());
        expect(span.children![0].toPlainText(), '[link ');

        expectLinkTextSpan(span.children![1] as TextSpan, 'bar');
        expectLinkTap(linkTapResults, const MarkdownLink('bar', '/uri'));
      },
    );

    testWidgets(
      // Example 523 from GFM.
      'link text with escaped open square bracket',
      (WidgetTester tester) async {
        const String data = r'[link \[bar](/uri)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link [bar');
        expectLinkTap(linkTapResults, const MarkdownLink('link [bar', '/uri'));
      },
    );

    testWidgets(
      // Example 524 from GFM.
      'link text with inline emphasis and code',
      (WidgetTester tester) async {
        const String data = '[link *foo **bar** `#`*](/uri)';
        final List<MarkdownLink> linkTapResults = <MarkdownLink>[];
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) =>
                  linkTapResults.add(MarkdownLink(text, href, title)),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 5);
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);
        expectTextSpanStyle(span.children![1] as TextSpan, FontStyle.italic, FontWeight.normal);
        expectTextSpanStyle(span.children![2] as TextSpan, FontStyle.italic, FontWeight.bold);
        expectTextSpanStyle(span.children![3] as TextSpan, FontStyle.italic, FontWeight.normal);
        expect((span.children![4] as TextSpan).style!.fontFamily, 'monospace');

        final List<Type> gestureRecognizerTypes = <Type>[];
        span.visitChildren((InlineSpan inlineSpan) {
          if (inlineSpan is TextSpan) {
            final TapGestureRecognizer? recognizer = inlineSpan.recognizer as TapGestureRecognizer?;
            gestureRecognizerTypes.add(recognizer.runtimeType);
            recognizer!.onTap!();
          }
          return true;
        });

        expect(gestureRecognizerTypes.length, 5);
        expect(gestureRecognizerTypes, everyElement(TapGestureRecognizer));
        expect(linkTapResults.length, 5);

        // Each of the child text span runs should return the same link info.
        for (final MarkdownLink tapResult in linkTapResults) {
          expectLinkTap(tapResult, const MarkdownLink('link foo bar #', '/uri'));
        }
      },
    );

    testWidgets(
      // Example 525 from GFM.
      'inline image link text',
      (WidgetTester tester) async {
        const String data = '[![moon](moon.jpg)](/uri)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Finder gestureFinder = find.byType(GestureDetector);
        expect(gestureFinder, findsOneWidget);
        final GestureDetector gestureWidget = gestureFinder.evaluate().first.widget as GestureDetector;
        expect(gestureWidget.child, isA<Image>());
        expect(gestureWidget.onTap, isNotNull);

        gestureWidget.onTap!();
        expectLinkTap(linkTapResults, const MarkdownLink('moon', '/uri'));
      },
    );

    testWidgets(
      // Example 526 from GFM.
      'links cannot be nested - outter link ignored',
      (WidgetTester tester) async {
        const String data = '[foo [bar](/uri)](/uri)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 3);
        expect(span.children![0], isA<TextSpan>());
        expect(span.children![0].toPlainText(), '[foo ');

        expectLinkTextSpan(span.children![1] as TextSpan, 'bar');
        expectLinkTap(linkTapResults, const MarkdownLink('bar', '/uri'));

        expect(span.children![2], isA<TextSpan>());
        expect(span.children![2].toPlainText(), '](/uri)');
      },
    );

    testWidgets(
      // Example 527 from GFM.
      'links cannot be nested - outter link ignored with emphasis',
      (WidgetTester tester) async {
        const String data = '[foo *[bar [baz](/uri)](/uri)*](/uri)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 5);
        expect(span.children, everyElement(isA<TextSpan>()));

        expect(span.children![0].toPlainText(), '[foo ');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);

        expect(span.children![1].toPlainText(), '[bar ');
        expectTextSpanStyle(span.children![1] as TextSpan, FontStyle.italic, FontWeight.normal);

        expect(span.children![2].toPlainText(), 'baz');
        expectTextSpanStyle(span.children![2] as TextSpan, FontStyle.italic, FontWeight.normal);

        expect(span.children![3].toPlainText(), '](/uri)');
        expectTextSpanStyle(span.children![3] as TextSpan, FontStyle.italic, FontWeight.normal);

        expect(span.children![4].toPlainText(), '](/uri)');
        expectTextSpanStyle(span.children![4] as TextSpan, null, FontWeight.normal);

        expectLinkTextSpan(span.children![2] as TextSpan, 'baz');
        expectLinkTap(linkTapResults, const MarkdownLink('baz', '/uri'));
      },
    );

    testWidgets(
      // Example 528 from GFM.
      'links cannot be nested in image linksinline image link text',
      (WidgetTester tester) async {
        const String data = '![[[foo](uri1)](uri2)](uri3)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Finder gestureFinder = find.byType(GestureDetector);
        expect(gestureFinder, findsNothing);

        final Finder imageFinder = find.byType(Image);
        expect(imageFinder, findsOneWidget);
        final Image image = imageFinder.evaluate().first.widget as Image;
        if (kIsWeb) {
          final NetworkImage fi = image.image as NetworkImage;
          expect(fi.url.endsWith('uri3'), true);
        } else {
          final FileImage fi = image.image as FileImage;
          expect(fi.file.path, equals('uri3'));
        }
        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 529 from GFM.
      'link text grouping has precedence over emphasis grouping example 1',
      (WidgetTester tester) async {
        const String data = r'*[foo*](/uri)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);
        expect(span.children![0], isA<TextSpan>());
        expect(span.children![0].toPlainText(), '*');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);

        expectLinkTextSpan(span.children![1] as TextSpan, 'foo*');
        expectLinkTap(linkTapResults, const MarkdownLink('foo*', '/uri'));
      },
    );

    testWidgets(
      // Example 530 from GFM.
      'link text grouping has precedence over emphasis grouping example 2',
      (WidgetTester tester) async {
        const String data = '[foo *bar](baz*)';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('foo *bar');
        expectLinkTap(linkTapResults, const MarkdownLink('foo *bar', 'baz*'));
      },
    );

    testWidgets(
      // Example 531 from GFM.
      "brackets that aren't part of links do not take precedence",
      (WidgetTester tester) async {
        const String data = '*foo [bar* baz]';
        await tester.pumpWidget(
          boilerplate(
            const MarkdownBody(data: data),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);
        expect(span.children, everyElement(isA<TextSpan>()));

        expect(span.children![0].toPlainText(), 'foo [bar');
        expectTextSpanStyle(span.children![0] as TextSpan, FontStyle.italic, FontWeight.normal);

        expect(span.children![1].toPlainText(), ' baz]');
        expectTextSpanStyle(span.children![1] as TextSpan, null, FontWeight.normal);
      },
    );

    testWidgets(
      // Example 532 from GFM.
      'HTML tag takes precedence over link grouping',
      (WidgetTester tester) async {
        const String data = '[foo <bar attr="](baz)">';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        // Link is treated as ordinary text.
        expectInvalidLink('[foo <bar attr="](baz)">');
        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 533 from GFM.
      'code span takes precedence over link grouping',
      (WidgetTester tester) async {
        const String data = '[foo`](/uri)`';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Finder gestureFinder = find.byType(GestureDetector);
        expect(gestureFinder, findsNothing);

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);
        expect(span.children, everyElement(isA<TextSpan>()));

        expect(span.children![0], isA<TextSpan>());
        expect(span.children![0].toPlainText(), '[foo');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);

        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);
        expect((span.children![1] as TextSpan).style!.fontFamily, 'monospace');
        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 534 from GFM.
      'autolinks take precedence over link grouping',
      (WidgetTester tester) async {
        const String data = '[foo<http://example.com/?search=](uri)>';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);
        expect(span.children, everyElement(isA<TextSpan>()));

        expect(span.children![0].toPlainText(), '[foo');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);

        expectLinkTextSpan(span.children![1] as TextSpan, 'http://example.com/?search=](uri)');
        expectLinkTap(linkTapResults,
            const MarkdownLink('http://example.com/?search=](uri)', 'http://example.com/?search=%5D(uri)'));
      },
    );
  });
  group('Reference Link', () {
    testWidgets(
      // Example 535 from GFM.
      'simple reference link',
      (WidgetTester tester) async {
        const String data = '[foo][bar]\n\n[bar]: /url "title"';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('foo');
        expectLinkTap(linkTapResults, const MarkdownLink('foo', '/url', 'title'));
      },
    );

    testWidgets(
      // Example 536 from GFM.
      'reference link with balanced brackets in link text',
      (WidgetTester tester) async {
        const String data = '[link [foo [bar]]][ref]\n\n[ref]: /uri';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link [foo [bar]]');
        expectLinkTap(linkTapResults, const MarkdownLink('link [foo [bar]]', '/uri'));
      },
    );

    testWidgets(
      // Example 537 from GFM.
      'reference link with unbalanced but escaped bracket in link text',
      (WidgetTester tester) async {
        const String data = '[link \\[bar][ref]\n\n[ref]: /uri';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('link [bar');
        expectLinkTap(linkTapResults, const MarkdownLink('link [bar', '/uri'));
      },
    );

    testWidgets(
      // Example 538 from GFM.
      'reference link with inline emphasis and code span in link text',
      (WidgetTester tester) async {
        const String data = '[link *foo **bar** `#`*][ref]\n\n[ref]: /uri';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 5);
        expect(span.children, everyElement(isA<TextSpan>()));

        expect(span.children![0].toPlainText(), 'link ');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);

        expect(span.children![1].toPlainText(), 'foo ');
        expectTextSpanStyle(span.children![1] as TextSpan, FontStyle.italic, FontWeight.normal);

        expect(span.children![2].toPlainText(), 'bar');
        expectTextSpanStyle(span.children![2] as TextSpan, FontStyle.italic, FontWeight.bold);

        expect(span.children![3].toPlainText(), ' ');
        expectTextSpanStyle(span.children![3] as TextSpan, FontStyle.italic, FontWeight.normal);

        expect(span.children![4].toPlainText(), '#');
        expectTextSpanStyle(span.children![4] as TextSpan, null, FontWeight.normal);
        expect((span.children![4] as TextSpan).style!.fontFamily, 'monospace');

        for (final InlineSpan element in span.children!) {
          final TextSpan textSpan = element as TextSpan;
          expect(textSpan.recognizer, isNotNull);
          expect(textSpan.recognizer, isA<TapGestureRecognizer>());
          final TapGestureRecognizer? tapRecognizer = textSpan.recognizer as TapGestureRecognizer?;
          expect(tapRecognizer?.onTap, isNotNull);

          tapRecognizer!.onTap!();
          expectLinkTap(linkTapResults, const MarkdownLink('link foo bar #', '/uri'));

          // Clear link tap results.
          linkTapResults = null;
        }
      },
    );

    testWidgets(
      // Example 539 from GFM.
      'referenence link with inline image link text',
      (WidgetTester tester) async {
        const String data = '[![moon](moon.jpg)][ref]\n\n[ref]: /uri';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Finder gestureFinder = find.byType(GestureDetector);
        expect(gestureFinder, findsOneWidget);
        final GestureDetector gestureWidget = gestureFinder.evaluate().first.widget as GestureDetector;
        expect(gestureWidget.child, isA<Image>());
        expect(gestureWidget.onTap, isNotNull);

        gestureWidget.onTap!();
        expectLinkTap(linkTapResults, const MarkdownLink('moon', '/uri'));
        imageCache.clear();
      },
    );

    testWidgets(
      // Example 540 from GFM.
      'reference links cannot have nested links',
      (WidgetTester tester) async {
        const String data = '[foo [bar](/uri)][ref]\n\n[ref]: /uri';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 4);

        expect(span.children![0], isA<TextSpan>());
        expect(span.children![0].toPlainText(), '[foo ');

        expectLinkTextSpan(span.children![1] as TextSpan, 'bar');
        expectLinkTap(linkTapResults, const MarkdownLink('bar', '/uri'));

        expect(span.children![2], isA<TextSpan>());
        expect(span.children![2].toPlainText(), ']');

        expectLinkTextSpan(span.children![3] as TextSpan, 'ref');
        expectLinkTap(linkTapResults, const MarkdownLink('ref', '/uri'));
      },
    );

    testWidgets(
      // Example 541 from GFM.
      'reference links cannot have nested reference links',
      (WidgetTester tester) async {
        const String data = '[foo *bar [baz][ref]*][ref]\n\n[ref]: /uri';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 5);

        expect(span.children![0], isA<TextSpan>());
        expect(span.children![0].toPlainText(), '[foo ');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);

        expect(span.children![1], isA<TextSpan>());
        expect(span.children![1].toPlainText(), 'bar ');
        expectTextSpanStyle(span.children![1] as TextSpan, FontStyle.italic, FontWeight.normal);

        expectLinkTextSpan(span.children![2] as TextSpan, 'baz');
        expectTextSpanStyle(span.children![2] as TextSpan, FontStyle.italic, FontWeight.normal);
        expectLinkTap(linkTapResults, const MarkdownLink('baz', '/uri'));

        expect(span.children![3], isA<TextSpan>());
        expect(span.children![3].toPlainText(), ']');
        expectTextSpanStyle(span.children![3] as TextSpan, null, FontWeight.normal);

        expectLinkTextSpan(span.children![4] as TextSpan, 'ref');
        expectTextSpanStyle(span.children![4] as TextSpan, null, FontWeight.normal);
        expectLinkTap(linkTapResults, const MarkdownLink('ref', '/uri'));
      },
    );

    testWidgets(
      // Example 542 from GFM.
      'reference link text grouping has precedence over emphasis grouping example 1',
      (WidgetTester tester) async {
        const String data = '*[foo*][ref]\n\n[ref]: /uri';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);
        expect(span.children![0], isA<TextSpan>());
        expect(span.children![0].toPlainText(), '*');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);

        expectLinkTextSpan(span.children![1] as TextSpan, 'foo*');
        expectLinkTap(linkTapResults, const MarkdownLink('foo*', '/uri'));
      },
    );

    testWidgets(
      // Example 543 from GFM.
      'reference link text grouping has precedence over emphasis grouping example 2',
      (WidgetTester tester) async {
        const String data = '[foo *bar][ref]*\n\n[ref]: /uri';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);

        expectLinkTextSpan(span.children![0] as TextSpan, 'foo *bar');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);
        expectLinkTap(linkTapResults, const MarkdownLink('foo *bar', '/uri'));

        expect(span.children![1], isA<TextSpan>());
        expect(span.children![1].toPlainText(), '*');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);
      },
    );

    testWidgets(
      // Example 544 from GFM.
      'HTML tag takes precedence over reference link grouping',
      (WidgetTester tester) async {
        const String data = '[foo <bar attr="][ref]">\n\n[ref]: /uri';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        // Link is treated as ordinary text.
        expectInvalidLink('[foo <bar attr="][ref]">');
        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 545 from GFM.
      'code span takes precedence over reference link grouping',
      (WidgetTester tester) async {
        const String data = '[foo`][ref]`\n\n[ref]: /uri';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Finder gestureFinder = find.byType(GestureDetector);
        expect(gestureFinder, findsNothing);

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);
        expect(span.children, everyElement(isA<TextSpan>()));

        expect(span.children![0], isA<TextSpan>());
        expect(span.children![0].toPlainText(), '[foo');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);

        expect(span.children![1].toPlainText(), '][ref]');
        expect((span.children![1] as TextSpan).style!.fontFamily, 'monospace');
        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 534 from GFM.
      'autolinks take precedence over reference link grouping',
      (WidgetTester tester) async {
        const String data = '[foo<http://example.com/?search=][ref]>\n\n[ref]: /uri';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);
        expect(span.children, everyElement(isA<TextSpan>()));

        expect(span.children![0].toPlainText(), '[foo');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);

        expectLinkTextSpan(span.children![1] as TextSpan, 'http://example.com/?search=][ref]');
        expectLinkTap(linkTapResults,
            const MarkdownLink('http://example.com/?search=][ref]', 'http://example.com/?search=%5D%5Bref%5D'));
      },
    );

    testWidgets(
      // Example 547 from GFM.
      'reference link matching is case-insensitive',
      (WidgetTester tester) async {
        const String data = '[foo][BaR]\n\n[bar]: /url "title"';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('foo');
        expectLinkTap(linkTapResults, const MarkdownLink('foo', '/url', 'title'));
      },
    );

    testWidgets(
      // Example 548 from GFM.
      'reference link support Unicode case fold - GFM',
      (WidgetTester tester) async {
        const String data = '[ẞ]\n\n[SS]: /url';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('ẞ');
        expectLinkTap(linkTapResults, const MarkdownLink('ẞ', '/url', 'title'));
      },
      // TODO(mjordan56): Remove skip once the issue #333 in the markdown package
      // is fixed and released. https://github.com/dart-lang/markdown/issues/333
      skip: true,
    );

    testWidgets(
      // Example 536 from CommonMark. NOTE: The CommonMark and GFM specifications
      // use different examples for Unicode case folding. Both are being added
      // to the test suite since each example produces different cases to test.
      'reference link support Unicode case fold - CommonMark',
      (WidgetTester tester) async {
        const String data = '[Толпой][Толпой] is a Russian word.\n\n[ТОЛПОЙ]: /url';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);

        expectLinkTextSpan(span.children![0] as TextSpan, 'Толпой');
        expectLinkTap(linkTapResults, const MarkdownLink('Толпой', '/url'));

        expect(span.children![1], isA<TextSpan>());
        expect(span.children![1].toPlainText(), ' is a Russian word.');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);
      },
    );

    testWidgets(
      // Example 549 from GFM.
      'reference link with internal whitespace',
      (WidgetTester tester) async {
        const String data = '[Foo\n  bar]: /url\n\n[Baz][Foo bar]';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('Baz');
        expectLinkTap(linkTapResults, const MarkdownLink('Baz', '/url'));
      },
    );

    testWidgets(
      // Example 550 from GFM.
      'reference link no whitespace between link text and link label',
      (WidgetTester tester) async {
        const String data = '[foo] [bar]\n\n[bar]: /url "title"';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);

        expect(span.children![0], isA<TextSpan>());
        expect(span.children![0].toPlainText(), '[foo] ');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);

        expectLinkTextSpan(span.children![1] as TextSpan, 'bar');
        expectLinkTap(linkTapResults, const MarkdownLink('bar', '/url', 'title'));
      },
    );

    testWidgets(
      // Example 551 from GFM.
      'reference link no line break between link text and link label',
      (WidgetTester tester) async {
        const String data = '[foo]\n[bar]\n\n[bar]: /url "title"';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);

        expect(span.children![0], isA<TextSpan>());
        expect(span.children![0].toPlainText(), '[foo] ');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);

        expectLinkTextSpan(span.children![1] as TextSpan, 'bar');
        expectLinkTap(linkTapResults, const MarkdownLink('bar', '/url', 'title'));
      },
    );

    testWidgets(
      // Example 552 from GFM.
      'multiple matching reference link definitions use first definition',
      (WidgetTester tester) async {
        const String data = '[foo]: /url1\n\n[foo]: /url2\n\n[bar][foo]';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('bar');
        expectLinkTap(linkTapResults, const MarkdownLink('bar', '/url1'));
      },
    );

    testWidgets(
      // Example 553 from GFM.
      'reference link matching is performed on normalized strings',
      (WidgetTester tester) async {
        const String data = '[bar][foo\\!]\n\n[foo!]: /url';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        // Link is treated as ordinary text.
        expectInvalidLink('[bar][foo!]');
        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 554 from GFM.
      'reference link labels cannot contain brackets - case 1',
      (WidgetTester tester) async {
        const String data = '[foo][ref[]\n\n[ref[]: /uri';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final List<RichText> textWidgets = tester.widgetList(find.byType(RichText)).toList().cast<RichText>();
        expect(textWidgets.length, 2);

        expect(textWidgets[0].text, isA<TextSpan>());
        expect(textWidgets[0].text.toPlainText(), '[foo][ref[]');
        expectTextSpanStyle(textWidgets[0].text as TextSpan, null, FontWeight.normal);

        expect(textWidgets[1].text, isA<TextSpan>());
        expect(textWidgets[1].text.toPlainText(), '[ref[]: /uri');
        expectTextSpanStyle(textWidgets[1].text as TextSpan, null, FontWeight.normal);

        expect(linkTapResults, isNull);
      },
      // TODO(mjordan56): Remove skip once the issue #335 in the markdown package
      // is fixed and released. https://github.com/dart-lang/markdown/issues/335
      skip: true,
    );

    testWidgets(
      // Example 555 from GFM.
      'reference link labels cannot contain brackets - case 2',
      (WidgetTester tester) async {
        const String data = '[foo][ref[bar]]\n\n[ref[bar]]: /uri';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final List<Text> textWidgets = tester.widgetList(find.byType(Text)).toList().cast<Text>();
        expect(textWidgets.length, 2);

        expect(textWidgets[0].textSpan, isNotNull);
        expect(textWidgets[0].textSpan, isA<TextSpan>());
        expect(textWidgets[0].textSpan!.toPlainText(), '[foo][ref[bar]]');
        expectTextSpanStyle(textWidgets[0].textSpan! as TextSpan, null, FontWeight.normal);

        expect(textWidgets[1].textSpan, isNotNull);
        expect(textWidgets[1].textSpan, isA<TextSpan>());
        expect(textWidgets[1].textSpan!.toPlainText(), '[ref[bar]]: /uri');
        expectTextSpanStyle(textWidgets[1].textSpan! as TextSpan, null, FontWeight.normal);

        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 556 from GFM.
      'reference link labels cannot contain brackets - case 3',
      (WidgetTester tester) async {
        const String data = '[[[foo]]]\n\n[[[foo]]]: /url';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final List<Text> textWidgets = tester.widgetList(find.byType(Text)).toList().cast<Text>();
        expect(textWidgets.length, 2);

        expect(textWidgets[0].textSpan, isNotNull);
        expect(textWidgets[0].textSpan, isA<TextSpan>());
        expect(textWidgets[0].textSpan!.toPlainText(), '[[[foo]]]');
        expectTextSpanStyle(textWidgets[0].textSpan! as TextSpan, null, FontWeight.normal);

        expect(textWidgets[1].textSpan, isNotNull);
        expect(textWidgets[1].textSpan, isA<TextSpan>());
        expect(textWidgets[1].textSpan!.toPlainText(), '[[[foo]]]: /url');
        expectTextSpanStyle(textWidgets[1].textSpan! as TextSpan, null, FontWeight.normal);

        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 557 from GFM.
      'reference link labels can have escaped brackets',
      (WidgetTester tester) async {
        const String data = '[foo][ref\\[]\n\n[ref\\[]: /uri';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('foo');
        expectLinkTap(linkTapResults, const MarkdownLink('foo', '/uri'));
      },
    );

    testWidgets(
      // Example 558 from GFM.
      'reference link labels can have escaped characters',
      (WidgetTester tester) async {
        const String data = '[bar\\]: /uri\n\n[bar\\]';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink(r'bar\');
        expectLinkTap(linkTapResults, const MarkdownLink(r'bar\', '/uri'));
      },
      // TODO(mjordan56): Remove skip once the issue #336 in the markdown package
      // is fixed and released. https://github.com/dart-lang/markdown/issues/336
      skip: true,
    );

    testWidgets(
      // Example 559 from GFM.
      'reference link labels must contain at least on non-whitespace character - case 1',
      (WidgetTester tester) async {
        const String data = '[]\n\n[]: /uri';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final List<Text> textWidgets = tester.widgetList(find.byType(Text)).toList().cast<Text>();
        expect(textWidgets.length, 2);

        expect(textWidgets[0].textSpan, isNotNull);
        expect(textWidgets[0].textSpan, isA<TextSpan>());
        expect(textWidgets[0].textSpan!.toPlainText(), '[]');
        expectTextSpanStyle(textWidgets[0].textSpan! as TextSpan, null, FontWeight.normal);

        expect(textWidgets[1].textSpan, isNotNull);
        expect(textWidgets[1].textSpan, isA<TextSpan>());
        expect(textWidgets[1].textSpan!.toPlainText(), '[]: /uri');
        expectTextSpanStyle(textWidgets[1].textSpan! as TextSpan, null, FontWeight.normal);

        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 560 from GFM.
      'reference link labels must contain at least on non-whitespace character - case 2',
      (WidgetTester tester) async {
        const String data = '[\n ]\n\n[\n ]: /uri';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final List<Text> textWidgets = tester.widgetList(find.byType(Text)).toList().cast<Text>();
        expect(textWidgets.length, 2);

        expect(textWidgets[0].textSpan, isNotNull);
        expect(textWidgets[0].textSpan, isA<TextSpan>());
        expect(textWidgets[0].textSpan!.toPlainText(), '[ ]');
        expectTextSpanStyle(textWidgets[0].textSpan! as TextSpan, null, FontWeight.normal);

        expect(textWidgets[1].textSpan, isNotNull);
        expect(textWidgets[1].textSpan, isA<TextSpan>());
        expect(textWidgets[1].textSpan!.toPlainText(), '[ ]: /uri');
        expectTextSpanStyle(textWidgets[1].textSpan! as TextSpan, null, FontWeight.normal);

        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 561 from GFM.
      'collapsed reference link',
      (WidgetTester tester) async {
        const String data = '[foo][]\n\n[foo]: /url "title"';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('foo');
        expectLinkTap(linkTapResults, const MarkdownLink('foo', '/url', 'title'));
      },
    );

    testWidgets(
      // Example 562 from GFM.
      'collapsed reference link with inline emphasis in link text',
      (WidgetTester tester) async {
        const String data = '[*foo* bar][]\n\n[*foo* bar]: /url "title"';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);
        expect(span.children, everyElement(isA<TextSpan>()));

        expect(span.children![0].toPlainText(), 'foo');
        expectTextSpanStyle(span.children![0] as TextSpan, FontStyle.italic, FontWeight.normal);

        expect(span.children![1].toPlainText(), ' bar');
        expectTextSpanStyle(span.children![1] as TextSpan, null, FontWeight.normal);

        for (final InlineSpan element in span.children!) {
          final TextSpan textSpan = element as TextSpan;
          expect(textSpan.recognizer, isNotNull);
          expect(textSpan.recognizer, isA<TapGestureRecognizer>());
          final TapGestureRecognizer? tapRecognizer = textSpan.recognizer as TapGestureRecognizer?;
          expect(tapRecognizer?.onTap, isNotNull);

          tapRecognizer!.onTap!();
          expectLinkTap(linkTapResults, const MarkdownLink('foo bar', '/url', 'title'));

          // Clear link tap results.
          linkTapResults = null;
        }
      },
    );

    testWidgets(
      // Example 563 from GFM.
      'collapsed reference links are case-insensitive',
      (WidgetTester tester) async {
        const String data = '[Foo][]\n\n[foo]: /url "title"';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('Foo');
        expectLinkTap(linkTapResults, const MarkdownLink('Foo', '/url', 'title'));
      },
    );

    testWidgets(
      // Example 564 from GFM.
      'collapsed reference link no whitespace between link text and link label',
      (WidgetTester tester) async {
        const String data = '[foo] \n\n[]\n\n[foo]: /url "title"';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final List<Text> textWidgets = tester.widgetList(find.byType(Text)).toList().cast<Text>();
        expect(textWidgets.length, 2);

        expect(textWidgets[0].textSpan, isNotNull);
        expect(textWidgets[0].textSpan, isA<TextSpan>());
        expect(textWidgets[0].textSpan!.toPlainText(), 'foo');

        expect(textWidgets[0].textSpan, isNotNull);
        expect(textWidgets[0].textSpan, isA<TextSpan>());
        expectLinkTextSpan(textWidgets[0].textSpan! as TextSpan, 'foo');
        expectLinkTap(linkTapResults, const MarkdownLink('foo', '/url', 'title'));

        expect(textWidgets[1].textSpan, isNotNull);
        expect(textWidgets[1].textSpan, isA<TextSpan>());
        expect(textWidgets[1].textSpan!.toPlainText(), '[]');
        expectTextSpanStyle(textWidgets[1].textSpan! as TextSpan, null, FontWeight.normal);
      },
    );

    testWidgets(
      // Example 565 from GFM.
      'shortcut reference link',
      (WidgetTester tester) async {
        const String data = '[foo]\n\n[foo]: /url "title"';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('foo');
        expectLinkTap(linkTapResults, const MarkdownLink('foo', '/url', 'title'));
      },
    );

    testWidgets(
      // Example 566 from GFM.
      'shortcut reference link with inline emphasis in link text',
      (WidgetTester tester) async {
        const String data = '[*foo* bar]\n\n[*foo* bar]: /url "title"';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);
        expect(span.children, everyElement(isA<TextSpan>()));

        expect(span.children![0].toPlainText(), 'foo');
        expectTextSpanStyle(span.children![0] as TextSpan, FontStyle.italic, FontWeight.normal);

        expect(span.children![1].toPlainText(), ' bar');
        expectTextSpanStyle(span.children![1] as TextSpan, null, FontWeight.normal);

        for (final InlineSpan element in span.children!) {
          final TextSpan textSpan = element as TextSpan;
          expect(textSpan.recognizer, isNotNull);
          expect(textSpan.recognizer, isA<TapGestureRecognizer>());
          final TapGestureRecognizer? tapRecognizer = textSpan.recognizer as TapGestureRecognizer?;
          expect(tapRecognizer?.onTap, isNotNull);

          tapRecognizer!.onTap!();
          expectLinkTap(linkTapResults, const MarkdownLink('foo bar', '/url', 'title'));

          // Clear link tap results.
          linkTapResults = null;
        }
      },
    );

    testWidgets(
      // Example 567 from GFM.
      'shortcut reference link with inline emphasis nested in link text',
      (WidgetTester tester) async {
        const String data = '[*foo* bar]\n\n[*foo* bar]: /url "title"';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);
        expect(span.children, everyElement(isA<TextSpan>()));

        expect(span.children![0].toPlainText(), 'foo');
        expectTextSpanStyle(span.children![0] as TextSpan, FontStyle.italic, FontWeight.normal);

        expect(span.children![1].toPlainText(), ' bar');
        expectTextSpanStyle(span.children![1] as TextSpan, null, FontWeight.normal);

        for (final InlineSpan element in span.children!) {
          final TextSpan textSpan = element as TextSpan;
          expect(textSpan.recognizer, isNotNull);
          expect(textSpan.recognizer, isA<TapGestureRecognizer>());
          final TapGestureRecognizer? tapRecognizer = textSpan.recognizer as TapGestureRecognizer?;
          expect(tapRecognizer?.onTap, isNotNull);

          tapRecognizer!.onTap!();
          expectLinkTap(linkTapResults, const MarkdownLink('foo bar', '/url', 'title'));

          // Clear link tap results.
          linkTapResults = null;
        }
      },
    );

    testWidgets(
      // Example 568 from GFM.
      'shortcut reference link with unbalanced open square brackets',
      (WidgetTester tester) async {
        const String data = '[[bar [foo]\n\n[foo]: /url';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);

        expect(span.children![0], isA<TextSpan>());
        expect(span.children![0].toPlainText(), '[[bar ');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);

        expectLinkTextSpan(span.children![1] as TextSpan, 'foo');
        expectLinkTap(linkTapResults, const MarkdownLink('foo', '/url'));
      },
    );

    testWidgets(
      // Example 569 from GFM.
      'shortcut reference links are case-insensitive',
      (WidgetTester tester) async {
        const String data = '[Foo]\n\n[foo]: /url "title"';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('Foo');
        expectLinkTap(linkTapResults, const MarkdownLink('Foo', '/url', 'title'));
      },
    );

    testWidgets(
      // Example 570 from GFM.
      'shortcut reference link should preserve space after link text',
      (WidgetTester tester) async {
        const String data = '[foo] bar\n\n[foo]: /url';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);

        expectLinkTextSpan(span.children![0] as TextSpan, 'foo');
        expectLinkTap(linkTapResults, const MarkdownLink('foo', '/url'));

        expect(span.children![1], isA<TextSpan>());
        expect(span.children![1].toPlainText(), ' bar');
        expectTextSpanStyle(span.children![1] as TextSpan, null, FontWeight.normal);
      },
    );

    testWidgets(
      // Example 571 from GFM.
      'shortcut reference link backslash escape opening bracket to avoid link',
      (WidgetTester tester) async {
        const String data = '\\[foo]\n\n[foo]: /url "title"';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        // Link is treated as ordinary text.
        expectInvalidLink('[foo]');
        expect(linkTapResults, isNull);
      },
    );

    testWidgets(
      // Example 572 from GFM.
      'shortcut reference link text grouping has precedence over emphasis grouping',
      (WidgetTester tester) async {
        const String data = '[foo*]: /url\n\n*[foo*]';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);
        expect(span.children![0], isA<TextSpan>());
        expect(span.children![0].toPlainText(), '*');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);

        expectLinkTextSpan(span.children![1] as TextSpan, 'foo*');
        expectLinkTap(linkTapResults, const MarkdownLink('foo*', '/url'));
      },
    );

    testWidgets(
      // Example 573 from GFM.
      'full link reference takes precedence over shortcut link reference',
      (WidgetTester tester) async {
        const String data = '[foo][bar]\n\n[foo]: /url1\n[bar]: /url2';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('foo');
        expectLinkTap(linkTapResults, const MarkdownLink('foo', '/url2'));
      },
    );

    testWidgets(
      // Example 574 from GFM.
      'compact link reference takes precedence over shortcut link reference',
      (WidgetTester tester) async {
        const String data = '[foo][]\n\n[foo]: /url1';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('foo');
        expectLinkTap(linkTapResults, const MarkdownLink('foo', '/url1'));
      },
    );

    testWidgets(
      // Example 575 from GFM.
      'inline link reference, no link destination takes precedence over shortcut link reference',
      (WidgetTester tester) async {
        const String data = '[foo]()\n\n[foo]: /url1';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        expectValidLink('foo');
        expectLinkTap(linkTapResults, const MarkdownLink('foo', ''));
      },
    );

    testWidgets(
      // Example 576 from GFM.
      'inline link reference, invalid link destination is a link followed by text',
      (WidgetTester tester) async {
        const String data = '[foo](not a link)\n\n[foo]: /url1';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);

        expectLinkTextSpan(span.children![0] as TextSpan, 'foo');
        expectLinkTap(linkTapResults, const MarkdownLink('foo', '/url1'));

        expect(span.children![1], isA<TextSpan>());
        expect(span.children![1].toPlainText(), '(not a link)');
        expectTextSpanStyle(span.children![1] as TextSpan, null, FontWeight.normal);
      },
    );

    testWidgets(
      // Example 577 from GFM.
      'three sequential runs of square-bracketed text, normal text and a link reference',
      (WidgetTester tester) async {
        const String data = '[foo][bar][baz]\n\n[baz]: /url';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);

        expect(span.children![0], isA<TextSpan>());
        expect(span.children![0].toPlainText(), '[foo]');
        expectTextSpanStyle(span.children![0] as TextSpan, null, FontWeight.normal);

        expectLinkTextSpan(span.children![1] as TextSpan, 'bar');
        expectLinkTap(linkTapResults, const MarkdownLink('bar', '/url'));
      },
    );

    testWidgets(
      // Example 578 from GFM.
      'three sequential runs of square-bracketed text, two link references',
      (WidgetTester tester) async {
        const String data = '[foo][bar][baz]\n\n[baz]: /url1\n[bar]: /url2';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);

        expectLinkTextSpan(span.children![0] as TextSpan, 'foo');
        expectLinkTap(linkTapResults, const MarkdownLink('foo', '/url2'));

        expectLinkTextSpan(span.children![1] as TextSpan, 'baz');
        expectLinkTap(linkTapResults, const MarkdownLink('baz', '/url1'));
      },
    );

    testWidgets(
      // Example 579 from GFM.
      'full reference link followed by a shortcut reference link',
      (WidgetTester tester) async {
        const String data = '[foo][bar][baz]\n\n[baz]: /url1\n[foo]: /url2';
        MarkdownLink? linkTapResults;
        await tester.pumpWidget(
          boilerplate(
            MarkdownBody(
              data: data,
              onTapLink: (String text, String? href, String title) => linkTapResults = MarkdownLink(text, href, title),
            ),
          ),
        );

        final Text textWidget = tester.widget(find.byType(Text));
        final TextSpan span = textWidget.textSpan! as TextSpan;
        expect(span.children!.length, 2);
        expect(span.children![0], isA<TextSpan>());
        expect(span.children![0].toPlainText(), '[foo]');

        expectLinkTextSpan(span.children![1] as TextSpan, 'bar');
        expectLinkTap(linkTapResults, const MarkdownLink('bar', '/url1'));
      },
    );
  });
}
