227 lines
5.4 KiB
Dart
227 lines
5.4 KiB
Dart
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:marianum_mobile/widget/prosemirror/pm_document_view.dart';
|
|
import 'package:marianum_mobile/widget/prosemirror/pm_node.dart';
|
|
|
|
const _pngDataUri =
|
|
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR4nGP4DwQACfsD/fteaysAAAAASUVORK5CYII=';
|
|
|
|
Map<String, dynamic> _kitchenSinkDoc() => {
|
|
'type': 'doc',
|
|
'content': [
|
|
{
|
|
'type': 'heading',
|
|
'attrs': {'level': 1},
|
|
'content': [
|
|
{'type': 'text', 'text': 'Heading'},
|
|
],
|
|
},
|
|
{
|
|
'type': 'paragraph',
|
|
'content': [
|
|
{
|
|
'type': 'text',
|
|
'text': 'bold ',
|
|
'marks': [
|
|
{'type': 'bold'},
|
|
],
|
|
},
|
|
{
|
|
'type': 'text',
|
|
'text': 'link',
|
|
'marks': [
|
|
{
|
|
'type': 'link',
|
|
'attrs': {'href': 'https://example.org'},
|
|
},
|
|
],
|
|
},
|
|
{'type': 'hardBreak'},
|
|
{
|
|
'type': 'text',
|
|
'text': 'code',
|
|
'marks': [
|
|
{'type': 'code'},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
'type': 'bulletList',
|
|
'content': [
|
|
{
|
|
'type': 'listItem',
|
|
'content': [
|
|
{
|
|
'type': 'paragraph',
|
|
'content': [
|
|
{'type': 'text', 'text': 'item'},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
'type': 'callout',
|
|
'attrs': {'variant': 'info'},
|
|
'content': [
|
|
{
|
|
'type': 'paragraph',
|
|
'content': [
|
|
{'type': 'text', 'text': 'note'},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
'type': 'codeBlock',
|
|
'content': [
|
|
{'type': 'text', 'text': 'x = 1'},
|
|
],
|
|
},
|
|
{'type': 'horizontalRule'},
|
|
{
|
|
'type': 'image',
|
|
'attrs': {'src': _pngDataUri, 'align': 'center', 'width': '40%'},
|
|
},
|
|
{
|
|
'type': 'unknownWidget',
|
|
'content': [
|
|
{
|
|
'type': 'paragraph',
|
|
'content': [
|
|
{'type': 'text', 'text': 'fallback child'},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
'type': 'table',
|
|
'content': [
|
|
{
|
|
'type': 'tableRow',
|
|
'content': [
|
|
{
|
|
'type': 'tableHeader',
|
|
'attrs': {'colspan': 2},
|
|
'content': [
|
|
{
|
|
'type': 'paragraph',
|
|
'content': [
|
|
{'type': 'text', 'text': 'head'},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
'type': 'tableRow',
|
|
'content': [
|
|
{
|
|
'type': 'tableCell',
|
|
'content': [
|
|
{
|
|
'type': 'paragraph',
|
|
'content': [
|
|
{'type': 'text', 'text': 'a'},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
'type': 'tableCell',
|
|
'content': [
|
|
{
|
|
'type': 'paragraph',
|
|
'content': [
|
|
{'type': 'text', 'text': 'b'},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
Widget _host(Brightness brightness, {void Function(String href)? onLinkTap}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(
|
|
colorScheme: ColorScheme.fromSeed(
|
|
seedColor: const Color(0xFF993333),
|
|
brightness: brightness,
|
|
),
|
|
),
|
|
home: Scaffold(
|
|
body: SingleChildScrollView(
|
|
child: PmDocumentView(
|
|
doc: PmNode.fromJson(_kitchenSinkDoc()),
|
|
onLinkTap: onLinkTap,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void main() {
|
|
testWidgets('renders kitchen-sink doc in light theme without exception', (
|
|
tester,
|
|
) async {
|
|
await tester.pumpWidget(_host(Brightness.light));
|
|
await tester.pump();
|
|
|
|
expect(tester.takeException(), isNull);
|
|
expect(find.text('Heading'), findsOneWidget);
|
|
expect(find.text('note'), findsOneWidget);
|
|
expect(find.text('fallback child'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('renders kitchen-sink doc in dark theme without exception', (
|
|
tester,
|
|
) async {
|
|
await tester.pumpWidget(_host(Brightness.dark));
|
|
await tester.pump();
|
|
|
|
expect(tester.takeException(), isNull);
|
|
expect(find.text('Heading'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('link marks route through onLinkTap', (tester) async {
|
|
final tapped = <String>[];
|
|
await tester.pumpWidget(_host(Brightness.light, onLinkTap: tapped.add));
|
|
await tester.pump();
|
|
|
|
final richTexts = tester.widgetList<RichText>(find.byType(RichText));
|
|
for (final richText in richTexts) {
|
|
if (TapGestureRecognizerHarness.tapFirstLink(richText.text)) break;
|
|
}
|
|
|
|
expect(tapped, ['https://example.org']);
|
|
});
|
|
}
|
|
|
|
/// Walks a composed span tree and fires the first link recognizer it finds,
|
|
/// returning whether one was tapped.
|
|
class TapGestureRecognizerHarness {
|
|
static bool tapFirstLink(InlineSpan span) {
|
|
var tapped = false;
|
|
span.visitChildren((child) {
|
|
if (child is TextSpan) {
|
|
final recognizer = child.recognizer;
|
|
if (recognizer is TapGestureRecognizer && recognizer.onTap != null) {
|
|
recognizer.onTap!();
|
|
tapped = true;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
return tapped;
|
|
}
|
|
}
|