Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: a tag should not style as link if href is not provided #1265

Merged
merged 1 commit into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 16 additions & 20 deletions lib/src/builtins/interactive_element_builtin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,32 @@ import 'package:flutter_html/src/utils.dart';
import 'package:html/dom.dart' as dom;

/// Defines the way an anchor ('a') element is lexed and parsed.
///
/// An `<a>` element with no `href` attribute is not interactive and is thus
/// not handled by this BuiltIn.
class InteractiveElementBuiltIn extends HtmlExtension {
const InteractiveElementBuiltIn();

@override
Set<String> get supportedTags => {'a'};

@override
bool matches(ExtensionContext context) {
return supportedTags.contains(context.elementName) &&
context.attributes.containsKey("href");
}

@override
StyledElement prepare(
ExtensionContext context, List<StyledElement> children) {
if (context.attributes.containsKey('href')) {
return InteractiveElement(
name: context.elementName,
children: children,
href: context.attributes['href'],
style: Style(
color: Colors.blue,
textDecoration: TextDecoration.underline,
),
node: context.node,
elementId: context.id,
);
}
// When <a> tag have no href, it must be unclickable and without decoration.
return StyledElement(
return InteractiveElement(
name: context.elementName,
children: children,
style: Style(),
href: context.attributes['href'],
style: Style(
color: Colors.blue,
textDecoration: TextDecoration.underline,
),
node: context.node,
elementId: context.id,
);
Expand Down Expand Up @@ -72,10 +71,7 @@ class InteractiveElementBuiltIn extends HtmlExtension {
child: MultipleTapGestureDetector(
onTap: onTap,
child: GestureDetector(
key: AnchorKey.of(
context.parser.key,
context
.styledElement), //TODO this replaced context.key. Does it work?
key: AnchorKey.of(context.parser.key, context.styledElement),
onTap: onTap,
child: (childSpan as WidgetSpan).child,
),
Expand Down
1 change: 1 addition & 0 deletions lib/src/builtins/styled_element_builtin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class StyledElementBuiltIn extends HtmlExtension {

@override
Set<String> get supportedTags => {
"a",
"abbr",
"acronym",
"address",
Expand Down
2 changes: 2 additions & 0 deletions lib/src/processing/relative_sizes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class RelativeSizesProcessing {
/// applies relative calculations
static StyledElement _calculateRelativeValues(
StyledElement tree, double devicePixelRatio) {
tree.style.fontSize ??= FontSize.medium;

double remSize = (tree.style.fontSize?.value ?? FontSize.medium.value);

//If the root element has a rem-based fontSize, then give it the default
Expand Down
90 changes: 90 additions & 0 deletions test/elements/a_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_test/flutter_test.dart';

import '../test_data.dart';

void main() {
testWidgets('<a> test', (WidgetTester tester) async {
await tester.pumpWidget(
TestApp(
child: Html(
data: """<a>Hello, world!</a>""",
),
),
);
expect(find.text("Hello, world!", findRichText: true), findsOneWidget);
});

testWidgets('<a> test with href', (WidgetTester tester) async {
await tester.pumpWidget(
TestApp(
child: Html(
data: """<a href="https://example.com">Hello, world!</a>""",
),
),
);
expect(find.text("Hello, world!", findRichText: true), findsOneWidget);
});

testWidgets('<a> with widget child renders', (WidgetTester tester) async {
await tester.pumpWidget(
TestApp(
child: Html(
data: """<a href="https://example.com"><icon></icon></a>""",
extensions: [
TagExtension(
tagsToExtend: {"icon"},
child: const Icon(Icons.check),
),
],
),
),
);
expect(find.byIcon(Icons.check), findsOneWidget);
});

testWidgets('Tapping <a> test', (WidgetTester tester) async {
String tappedUrl = "";

await tester.pumpWidget(
TestApp(
child: Html(
data: """<a href="https://example.com">Hello, world!</a>""",
onLinkTap: (url, _, __) {
tappedUrl = url ?? "";
},
),
),
);
expect(find.text("Hello, world!", findRichText: true), findsOneWidget);
expect(tappedUrl, equals(""));
await tester.tap(find.text("Hello, world!", findRichText: true));
expect(tappedUrl, equals("https://example.com"));
});

testWidgets('Tapping <a> with widget works', (WidgetTester tester) async {
String tappedUrl = "";

await tester.pumpWidget(
TestApp(
child: Html(
data: """<a href="https://example.com"><icon></icon></a>""",
onLinkTap: (url, _, __) {
tappedUrl = url ?? "";
},
extensions: [
TagExtension(
tagsToExtend: {"icon"},
child: const Icon(Icons.check),
),
],
),
),
);
expect(find.byIcon(Icons.check), findsOneWidget);
expect(tappedUrl, equals(""));
await tester.tap(find.byIcon(Icons.check));
expect(tappedUrl, equals("https://example.com"));
});
}
17 changes: 17 additions & 0 deletions test/test_data.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
import 'package:flutter/material.dart';

class TestApp extends StatelessWidget {
final Widget child;

const TestApp({Key? key, required this.child}) : super(key: key);

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: child,
),
);
}
}

const testData = <String, String>{
'a': '<a>Hello, World!</a>',
'abbr': '<abbr>HLO-WRLD</abbr>',
Expand Down