parse(
String html, {
bool isSelectable = true,
@@ -201,19 +218,59 @@ class HTMLParser {
}
List classes = codeElement.classes.toList();
+ String codeText = codeElement.text.trimRight();
- return Editor(
- options: EditorOptions(
- fontFamily: 'Hack',
- takeFullHeight: false,
- isEditable: false,
- showLinebar: false,
+ return Container(
+ color: FccColors.gray80,
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Expanded(
+ child: Editor(
+ options: EditorOptions(
+ fontFamily: 'Hack',
+ takeFullHeight: false,
+ isEditable: false,
+ showLinebar: false,
+ ),
+ defaultLanguage: codeLanguageIsPresent(classes)
+ ? currentClass!.split('-')[1]
+ : '',
+ defaultValue: codeText,
+ path: 'example',
+ ),
+ ),
+ SelectionContainer.disabled(
+ child: Padding(
+ padding: const EdgeInsets.only(top: 4, right: 12),
+ child: Tooltip(
+ message: 'Copy code',
+ child: Semantics(
+ label: 'Copy code block',
+ button: true,
+ child: GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onTap: () {
+ _copyToClipboard(
+ codeText,
+ 'Code copied to clipboard!',
+ );
+ },
+ child: const SizedBox.square(
+ dimension: 28,
+ child: Icon(
+ Icons.copy,
+ size: 20,
+ color: FccSemanticColors.foregroundPrimary,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
),
- defaultLanguage: codeLanguageIsPresent(classes)
- ? currentClass!.split('-')[1]
- : '',
- defaultValue: codeElement.text.trimRight(),
- path: 'example',
);
},
),
@@ -253,29 +310,7 @@ class HTMLParser {
return InkWell(
onTap: () {
- Clipboard.setData(ClipboardData(text: parsed));
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: Text.rich(
- TextSpan(
- children: [
- TextSpan(
- text: parsed,
- style: const TextStyle(
- fontWeight: FontWeight.bold,
- fontSize: 20,
- ),
- ),
- const TextSpan(
- text: ' copied to clipboard!',
- style: TextStyle(fontSize: 20),
- ),
- ],
- ),
- ),
- duration: const Duration(seconds: 1),
- ),
- );
+ _copyToClipboard(parsed, '$parsed copied to clipboard!');
},
child: Text(
parsed,
diff --git a/mobile-app/test/widget/html_handler_test.dart b/mobile-app/test/widget/html_handler_test.dart
new file mode 100644
index 000000000..00a754270
--- /dev/null
+++ b/mobile-app/test/widget/html_handler_test.dart
@@ -0,0 +1,69 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:freecodecamp/ui/views/news/html_handler/html_handler.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+void main() {
+ TestWidgetsFlutterBinding.ensureInitialized();
+
+ String? clipboardText;
+
+ setUp(() {
+ SharedPreferences.setMockInitialValues({});
+ clipboardText = null;
+
+ TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
+ .setMockMethodCallHandler(SystemChannels.platform, (call) async {
+ switch (call.method) {
+ case 'Clipboard.setData':
+ clipboardText = call.arguments['text'] as String?;
+ return null;
+ case 'Clipboard.getData':
+ return {'text': clipboardText};
+ }
+
+ return null;
+ });
+ });
+
+ tearDown(() {
+ TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
+ .setMockMethodCallHandler(SystemChannels.platform, null);
+ });
+
+ Widget htmlParserFixture(String html) {
+ return MaterialApp(
+ home: Scaffold(
+ body: Builder(
+ builder: (context) {
+ final parser = HTMLParser(context: context);
+
+ return Column(
+ children: parser.parse(html),
+ );
+ },
+ ),
+ ),
+ );
+ }
+
+ testWidgets('copies the contents of a pre code block', (tester) async {
+ const code = 'Hello
\nconst answer = 42;';
+
+ await tester.pumpWidget(
+ htmlParserFixture(
+ '<p>Hello</p>\nconst answer = 42;
',
+ ),
+ );
+ await tester.pump();
+
+ expect(find.byTooltip('Copy code'), findsOneWidget);
+
+ await tester.tap(find.byIcon(Icons.copy));
+ await tester.pump();
+
+ expect(clipboardText, code);
+ expect(find.text('Code copied to clipboard!'), findsOneWidget);
+ });
+}