448 lines
15 KiB
Dart
448 lines
15 KiB
Dart
// Copyright 2013 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'dart:io' as io;
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'image_test_mocks.dart';
|
|
import 'utils.dart';
|
|
|
|
void main() => defineTests();
|
|
|
|
void defineTests() {
|
|
group('Image', () {
|
|
setUp(() {
|
|
// Only needs to be done once since the HttpClient generated
|
|
// by this override is cached as a static singleton.
|
|
io.HttpOverrides.global = TestHttpOverrides();
|
|
});
|
|
|
|
testWidgets(
|
|
'should not interrupt styling',
|
|
(WidgetTester tester) async {
|
|
const String data = '_textbefore  textafter_';
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
const Markdown(data: data),
|
|
),
|
|
);
|
|
|
|
final Iterable<Text> texts = tester.widgetList(find.byType(Text));
|
|
final Text firstTextWidget = texts.first;
|
|
final TextSpan firstTextSpan = firstTextWidget.textSpan! as TextSpan;
|
|
final Image image = tester.widget(find.byType(Image));
|
|
final NetworkImage networkImage = image.image as NetworkImage;
|
|
final Text secondTextWidget = texts.last;
|
|
final TextSpan secondTextSpan = secondTextWidget.textSpan! as TextSpan;
|
|
|
|
expect(firstTextSpan.text, 'textbefore ');
|
|
expect(firstTextSpan.style!.fontStyle, FontStyle.italic);
|
|
expect(networkImage.url, 'https://img');
|
|
expect(secondTextSpan.text, ' textafter');
|
|
expect(secondTextSpan.style!.fontStyle, FontStyle.italic);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'should work with a link',
|
|
(WidgetTester tester) async {
|
|
const String data = '';
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
const Markdown(data: data),
|
|
),
|
|
);
|
|
|
|
final Image image = tester.widget(find.byType(Image));
|
|
final NetworkImage networkImage = image.image as NetworkImage;
|
|
expect(networkImage.url, 'https://img');
|
|
expect(image.width, 50);
|
|
expect(image.height, 50);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'should work with relative remote image',
|
|
(WidgetTester tester) async {
|
|
const String data = '';
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
const Markdown(
|
|
data: data,
|
|
imageDirectory: 'https://localhost',
|
|
),
|
|
),
|
|
);
|
|
|
|
final Iterable<Widget> widgets = tester.allWidgets;
|
|
final Image image = widgets.firstWhere((Widget widget) => widget is Image) as Image;
|
|
|
|
expect(image.image is NetworkImage, isTrue);
|
|
expect((image.image as NetworkImage).url, 'https://localhost/img.png');
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'local files should be files on non-web',
|
|
(WidgetTester tester) async {
|
|
const String data = '';
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
const Markdown(data: data),
|
|
),
|
|
);
|
|
|
|
final Iterable<Widget> widgets = tester.allWidgets;
|
|
final Image image = widgets.firstWhere((Widget widget) => widget is Image) as Image;
|
|
|
|
expect(image.image is FileImage, isTrue);
|
|
},
|
|
skip: kIsWeb || isLinux,
|
|
);
|
|
|
|
testWidgets(
|
|
'local files should be network on web',
|
|
(WidgetTester tester) async {
|
|
const String data = '';
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
const Markdown(data: data),
|
|
),
|
|
);
|
|
|
|
final Iterable<Widget> widgets = tester.allWidgets;
|
|
final Image image = widgets.firstWhere((Widget widget) => widget is Image) as Image;
|
|
|
|
expect(image.image is NetworkImage, isTrue);
|
|
},
|
|
skip: !kIsWeb || !isLinux,
|
|
);
|
|
|
|
testWidgets(
|
|
'should work with resources',
|
|
(WidgetTester tester) async {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
const String data = '';
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
MaterialApp(
|
|
home: DefaultAssetBundle(
|
|
bundle: TestAssetBundle(),
|
|
child: Center(
|
|
child: Container(
|
|
color: Colors.white,
|
|
width: 500,
|
|
child: const Markdown(
|
|
data: data,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Image image = tester.allWidgets.firstWhere((Widget widget) => widget is Image) as Image;
|
|
|
|
expect(image.image is AssetImage, isTrue);
|
|
expect((image.image as AssetImage).assetName, 'assets/logo.png');
|
|
|
|
// Force the asset image to be rasterized so it can be compared.
|
|
await tester.runAsync(() async {
|
|
final Element element = tester.element(find.byType(Markdown));
|
|
await precacheImage(image.image, element);
|
|
});
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
await expectLater(
|
|
find.byType(Container), matchesGoldenFile('assets/images/golden/image_test/resource_asset_logo.png'));
|
|
},
|
|
skip: kIsWeb || isLinux, // Goldens are platform-specific.
|
|
);
|
|
|
|
testWidgets(
|
|
'should work with local image files',
|
|
(WidgetTester tester) async {
|
|
const String data = '';
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
const Markdown(data: data),
|
|
),
|
|
);
|
|
|
|
final Image image = tester.widget(find.byType(Image));
|
|
final FileImage fileImage = image.image as FileImage;
|
|
expect(fileImage.file.path, 'img.png');
|
|
expect(image.width, 50);
|
|
expect(image.height, 50);
|
|
},
|
|
skip: kIsWeb || isLinux,
|
|
);
|
|
|
|
testWidgets(
|
|
'should show properly next to text',
|
|
(WidgetTester tester) async {
|
|
const String data = 'Hello ';
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
const Markdown(data: data),
|
|
),
|
|
);
|
|
|
|
final Text text = tester.widget(find.byType(Text));
|
|
final TextSpan textSpan = text.textSpan! as TextSpan;
|
|
expect(textSpan.text, 'Hello ');
|
|
expect(textSpan.style, isNotNull);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'should work when nested in a link',
|
|
(WidgetTester tester) async {
|
|
final List<String> tapTexts = <String>[];
|
|
final List<String?> tapResults = <String?>[];
|
|
const String data = '[](href)';
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
Markdown(
|
|
data: data,
|
|
onTapLink: (String text, String? value, String title) {
|
|
tapTexts.add(text);
|
|
tapResults.add(value);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
final GestureDetector detector = tester.widget(find.byType(GestureDetector));
|
|
detector.onTap!();
|
|
|
|
expect(tapTexts.length, 1);
|
|
expect(tapTexts, everyElement('alt'));
|
|
expect(tapResults.length, 1);
|
|
expect(tapResults, everyElement('href'));
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'should work when nested in a link with text',
|
|
(WidgetTester tester) async {
|
|
final List<String> tapTexts = <String>[];
|
|
final List<String?> tapResults = <String?>[];
|
|
const String data = '[Text before  text after](href)';
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
Markdown(
|
|
data: data,
|
|
onTapLink: (String text, String? value, String title) {
|
|
tapTexts.add(text);
|
|
tapResults.add(value);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
final GestureDetector detector = tester.widget(find.byType(GestureDetector));
|
|
detector.onTap!();
|
|
|
|
final Iterable<Text> texts = tester.widgetList(find.byType(Text));
|
|
final Text firstTextWidget = texts.first;
|
|
final TextSpan firstSpan = firstTextWidget.textSpan! as TextSpan;
|
|
(firstSpan.recognizer as TapGestureRecognizer?)!.onTap!();
|
|
|
|
final Text lastTextWidget = texts.last;
|
|
final TextSpan lastSpan = lastTextWidget.textSpan! as TextSpan;
|
|
(lastSpan.recognizer as TapGestureRecognizer?)!.onTap!();
|
|
|
|
expect(firstSpan.children, null);
|
|
expect(firstSpan.text, 'Text before ');
|
|
expect(firstSpan.recognizer.runtimeType, equals(TapGestureRecognizer));
|
|
|
|
expect(lastSpan.children, null);
|
|
expect(lastSpan.text, ' text after');
|
|
expect(lastSpan.recognizer.runtimeType, equals(TapGestureRecognizer));
|
|
|
|
expect(tapTexts.length, 3);
|
|
expect(tapTexts, everyElement('Text before alt text after'));
|
|
expect(tapResults.length, 3);
|
|
expect(tapResults, everyElement('href'));
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'should work alongside different links',
|
|
(WidgetTester tester) async {
|
|
final List<String> tapTexts = <String>[];
|
|
final List<String?> tapResults = <String?>[];
|
|
const String data = '[Link before](firstHref)[](imageHref)[link after](secondHref)';
|
|
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
Markdown(
|
|
data: data,
|
|
onTapLink: (String text, String? value, String title) {
|
|
tapTexts.add(text);
|
|
tapResults.add(value);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
final Iterable<Text> texts = tester.widgetList(find.byType(Text));
|
|
final Text firstTextWidget = texts.first;
|
|
final TextSpan firstSpan = firstTextWidget.textSpan! as TextSpan;
|
|
(firstSpan.recognizer as TapGestureRecognizer?)!.onTap!();
|
|
|
|
final GestureDetector detector = tester.widget(find.byType(GestureDetector));
|
|
detector.onTap!();
|
|
|
|
final Text lastTextWidget = texts.last;
|
|
final TextSpan lastSpan = lastTextWidget.textSpan! as TextSpan;
|
|
(lastSpan.recognizer as TapGestureRecognizer?)!.onTap!();
|
|
|
|
expect(firstSpan.children, null);
|
|
expect(firstSpan.text, 'Link before');
|
|
expect(firstSpan.recognizer.runtimeType, equals(TapGestureRecognizer));
|
|
|
|
expect(lastSpan.children, null);
|
|
expect(lastSpan.text, 'link after');
|
|
expect(lastSpan.recognizer.runtimeType, equals(TapGestureRecognizer));
|
|
|
|
expect(tapTexts.length, 3);
|
|
expect(tapTexts, <String>['Link before', 'alt', 'link after']);
|
|
expect(tapResults.length, 3);
|
|
expect(tapResults, <String>['firstHref', 'imageHref', 'secondHref']);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'should gracefully handle image URLs with empty scheme',
|
|
(WidgetTester tester) async {
|
|
const String data = '';
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
const Markdown(data: data),
|
|
),
|
|
);
|
|
|
|
expect(find.byType(Image), findsNothing);
|
|
expect(tester.takeException(), isNull);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'should gracefully handle image URLs with invalid scheme',
|
|
(WidgetTester tester) async {
|
|
const String data = '';
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
const Markdown(data: data),
|
|
),
|
|
);
|
|
|
|
// On the web, any URI with an unrecognized scheme is treated as a network image.
|
|
// Thus the error builder of the Image widget is called.
|
|
// On non-web, any URI with an unrecognized scheme is treated as a file image.
|
|
// However, constructing a file from an invalid URI will throw an exception.
|
|
// Thus the Image widget is never created, nor is its error builder called.
|
|
if (kIsWeb || isLinux) {
|
|
expect(find.byType(Image), findsOneWidget);
|
|
} else {
|
|
expect(find.byType(Image), findsNothing);
|
|
}
|
|
|
|
expect(tester.takeException(), isNull);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'should gracefully handle width parsing failures',
|
|
(WidgetTester tester) async {
|
|
const String data = '';
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
const Markdown(data: data),
|
|
),
|
|
);
|
|
|
|
final Image image = tester.widget(find.byType(Image));
|
|
final NetworkImage networkImage = image.image as NetworkImage;
|
|
expect(networkImage.url, 'https://img');
|
|
expect(image.width, null);
|
|
expect(image.height, 50);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'should gracefully handle height parsing failures',
|
|
(WidgetTester tester) async {
|
|
const String data = ' ';
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
const Markdown(data: data),
|
|
),
|
|
);
|
|
|
|
final Image image = tester.widget(find.byType(Image));
|
|
final NetworkImage networkImage = image.image as NetworkImage;
|
|
expect(networkImage.url, 'https://img');
|
|
expect(image.width, 50);
|
|
expect(image.height, null);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'custom image builder',
|
|
(WidgetTester tester) async {
|
|
const String data = '';
|
|
Widget builder(Uri uri, String? title, String? alt) => Image.asset('assets/logo.png');
|
|
|
|
await tester.pumpWidget(
|
|
boilerplate(
|
|
MaterialApp(
|
|
home: DefaultAssetBundle(
|
|
bundle: TestAssetBundle(),
|
|
child: Center(
|
|
child: Container(
|
|
color: Colors.white,
|
|
width: 500,
|
|
child: Markdown(
|
|
data: data,
|
|
imageBuilder: builder,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Iterable<Widget> widgets = tester.allWidgets;
|
|
final Image image = widgets.firstWhere((Widget widget) => widget is Image) as Image;
|
|
|
|
expect(image.image.runtimeType, AssetImage);
|
|
expect((image.image as AssetImage).assetName, 'assets/logo.png');
|
|
|
|
// Force the asset image to be rasterized so it can be compared.
|
|
await tester.runAsync(() async {
|
|
final Element element = tester.element(find.byType(Markdown));
|
|
await precacheImage(image.image, element);
|
|
});
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
await expectLater(
|
|
find.byType(Container), matchesGoldenFile('assets/images/golden/image_test/custom_builder_asset_logo.png'));
|
|
imageCache.clear();
|
|
},
|
|
skip: kIsWeb || isLinux, // Goldens are platform-specific.
|
|
);
|
|
});
|
|
}
|