Skip to content

Commit

Permalink
Add GestureRecognizer to children of 'a' Tag (flutter#9)
Browse files Browse the repository at this point in the history
* Add GestureRecognizer to children of 'a' Tag

* Add support links with nested items

* Add test for nested elements in links

* Add onTap tests
  • Loading branch information
xqwzts authored and DaveShuckerow committed Nov 2, 2017
1 parent 21a04bc commit 4ead1ed
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 18 deletions.
26 changes: 18 additions & 8 deletions packages/flutter_markdown/lib/src/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ class MarkdownBuilder implements md.NodeVisitor {
final List<String> _listIndents = <String>[];
final List<_BlockElement> _blocks = <_BlockElement>[];
final List<_InlineElement> _inlines = <_InlineElement>[];
final List<GestureRecognizer> _linkHandlers = <GestureRecognizer>[];


/// Returns widgets that display the given Markdown nodes.
///
Expand All @@ -81,6 +83,7 @@ class MarkdownBuilder implements md.NodeVisitor {
_listIndents.clear();
_blocks.clear();
_inlines.clear();
_linkHandlers.clear();

_blocks.add(new _BlockElement(null));
_inlines.add(new _InlineElement());
Expand All @@ -98,8 +101,12 @@ class MarkdownBuilder implements md.NodeVisitor {
void visitText(md.Text text) {
if (_blocks.last.tag == null) // Don't allow text directly under the root.
return;
final TextSpan span = _blocks.last.tag == 'pre' ?
delegate.formatText(styleSheet, text.text) : new TextSpan(text: text.text);
final TextSpan span = _blocks.last.tag == 'pre'
? delegate.formatText(styleSheet, text.text)
: new TextSpan(
text: text.text,
recognizer: _linkHandlers.isNotEmpty ? _linkHandlers.last : null,
);
_inlines.last.children.add(span);
}

Expand All @@ -114,6 +121,11 @@ class MarkdownBuilder implements md.NodeVisitor {
} else {
_inlines.add(new _InlineElement());
}

if (tag == 'a') {
_linkHandlers.add(delegate.createLink(element.attributes['href']));
}

return true;
}

Expand Down Expand Up @@ -179,16 +191,14 @@ class MarkdownBuilder implements md.NodeVisitor {
final _InlineElement parent = _inlines.last;

if (current.children.isNotEmpty) {
GestureRecognizer recognizer;

if (tag == 'a')
recognizer = delegate.createLink(element.attributes['href']);

parent.children.add(new TextSpan(
style: styleSheet.styles[tag],
recognizer: recognizer,
children: current.children,
));

if (tag == 'a') {
_linkHandlers.removeLast();
}
}
}
}
Expand Down
87 changes: 77 additions & 10 deletions packages/flutter_markdown/test/flutter_markdown_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,83 @@ void main() {
]);
});

testWidgets('Links', (WidgetTester tester) async {
await tester
.pumpWidget(_boilerplate(const Markdown(data: '[Link Text](href)')));

final RichText textWidget =
tester.allWidgets.firstWhere((Widget widget) => widget is RichText);
final TextSpan span = textWidget.text;

expect(
span.children[0].recognizer.runtimeType, equals(TapGestureRecognizer));
group('Links', () {
testWidgets('Single link', (WidgetTester tester) async {
String tapResult;
await tester.pumpWidget(_boilerplate(new Markdown(
data: '[Link Text](href)',
onTapLink: (value) => tapResult = value,
)));

final RichText textWidget =
tester.allWidgets.firstWhere((Widget widget) => widget is RichText);
final TextSpan span = textWidget.text;

(span.children[0].children[0].recognizer as TapGestureRecognizer).onTap();

expect(span.children.length, 1);
expect(span.children[0].children.length, 1);
expect(span.children[0].children[0].recognizer.runtimeType,
equals(TapGestureRecognizer));
expect(tapResult, 'href');
});

testWidgets('Link with nested code', (WidgetTester tester) async {
final List<String> tapResults = <String>[];
await tester.pumpWidget(_boilerplate(new Markdown(
data: '[Link `with nested code` Text](href)',
onTapLink: (value) => tapResults.add(value),
)));

final RichText textWidget =
tester.allWidgets.firstWhere((Widget widget) => widget is RichText);
final TextSpan span = textWidget.text;

final List<Type> gestureRecognizerTypes = <Type>[];
span.visitTextSpan((TextSpan textSpan) {
TapGestureRecognizer recognizer = textSpan.recognizer;
gestureRecognizerTypes.add(recognizer.runtimeType);
recognizer.onTap();
return true;
});

expect(span.children.length, 1);
expect(span.children[0].children.length, 3);
expect(gestureRecognizerTypes, everyElement(TapGestureRecognizer));
expect(tapResults.length, 3);
expect(tapResults, everyElement('href'));
});

testWidgets('Multiple links', (WidgetTester tester) async {
final List<String> tapResults = <String>[];

await tester.pumpWidget(_boilerplate(new Markdown(
data: '[First Link](firstHref) and [Second Link](secondHref)',
onTapLink: (value) => tapResults.add(value),
)));

final RichText textWidget =
tester.allWidgets.firstWhere((Widget widget) => widget is RichText);
final TextSpan span = textWidget.text;

final List<Type> gestureRecognizerTypes = <Type>[];
span.visitTextSpan((TextSpan textSpan) {
TapGestureRecognizer recognizer = textSpan.recognizer;
gestureRecognizerTypes.add(recognizer.runtimeType);
recognizer?.onTap();
return true;
});


expect(span.children.length, 3);
expect(span.children[0].children.length, 1);
expect(span.children[1].children, null);
expect(span.children[2].children.length, 1);

expect(gestureRecognizerTypes,
orderedEquals([TapGestureRecognizer, Null, TapGestureRecognizer]));
expect(tapResults, orderedEquals(['firstHref', 'secondHref']));
});
});

testWidgets('HTML tag ignored ', (WidgetTester tester) async {
Expand Down

0 comments on commit 4ead1ed

Please sign in to comment.