Files
Client/test/widget/prosemirror/pm_document_view_test.dart
T

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;
}
}