widget_svg_test.dart 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'dart:io';
  4. import 'dart:typed_data';
  5. import 'dart:ui' show window;
  6. import 'package:flutter/rendering.dart';
  7. import 'package:flutter/widgets.dart';
  8. import 'package:flutter/services.dart';
  9. import 'package:flutter_test/flutter_test.dart';
  10. import 'package:flutter_svg/flutter_svg.dart';
  11. import 'package:mockito/mockito.dart';
  12. Future<void> _checkWidgetAndGolden(Key key, String filename) async {
  13. final Finder widgetFinder = find.byKey(key);
  14. expect(widgetFinder, findsOneWidget);
  15. await expectLater(widgetFinder, matchesGoldenFile('golden_widget/$filename'));
  16. }
  17. void main() {
  18. const String svgStr =
  19. '''<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 166 202">
  20. <defs>
  21. <linearGradient id="triangleGradient">
  22. <stop offset="20%" stop-color="#000000" stop-opacity=".55" />
  23. <stop offset="85%" stop-color="#616161" stop-opacity=".01" />
  24. </linearGradient>
  25. <linearGradient id="rectangleGradient" x1="0%" x2="0%" y1="0%" y2="100%">
  26. <stop offset="20%" stop-color="#000000" stop-opacity=".15" />
  27. <stop offset="85%" stop-color="#616161" stop-opacity=".01" />
  28. </linearGradient>
  29. </defs>
  30. <path fill="#42A5F5" fill-opacity=".8" d="M37.7 128.9 9.8 101 100.4 10.4 156.2 10.4"/>
  31. <path fill="#42A5F5" fill-opacity=".8" d="M156.2 94 100.4 94 79.5 114.9 107.4 142.8"/>
  32. <path fill="#0D47A1" d="M79.5 170.7 100.4 191.6 156.2 191.6 156.2 191.6 107.4 142.8"/>
  33. <g transform="matrix(0.7071, -0.7071, 0.7071, 0.7071, -77.667, 98.057)">
  34. <rect width="39.4" height="39.4" x="59.8" y="123.1" fill="#42A5F5" />
  35. <rect width="39.4" height="5.5" x="59.8" y="162.5" fill="url(#rectangleGradient)" />
  36. </g>
  37. <path d="M79.5 170.7 120.9 156.4 107.4 142.8" fill="url(#triangleGradient)" />
  38. </svg>''';
  39. const String stickFigureSvgStr = '''<?xml version="1.0" encoding="UTF-8"?>
  40. <svg width="27px" height="90px" viewBox="5 10 18 70" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  41. <!-- Generator: Sketch 53 (72520) - https://sketchapp.com -->
  42. <title>svg/stick_figure</title>
  43. <desc>Created with Sketch.</desc>
  44. <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
  45. <g id="iPhone-8" transform="translate(-53.000000, -359.000000)" stroke="#979797">
  46. <g id="stick_figure" transform="translate(53.000000, 359.000000)">
  47. <ellipse id="Oval" fill="#D8D8D8" cx="13.5" cy="12" rx="12" ry="11.5"></ellipse>
  48. <path d="M13.5,24 L13.5,71.5" id="Line" stroke-linecap="square"></path>
  49. <path d="M13.5,71.5 L1,89.5" id="Line-2" stroke-linecap="square"></path>
  50. <path d="M13.5,37.5 L1,55.5" id="Line-2-Copy-2" stroke-linecap="square"></path>
  51. <path d="M26.5,71.5 L14,89.5" id="Line-2" stroke-linecap="square" transform="translate(20.000000, 80.500000) scale(-1, 1) translate(-20.000000, -80.500000) "></path>
  52. <path d="M26.5,37.5 L14,55.5" id="Line-2-Copy" stroke-linecap="square" transform="translate(20.000000, 46.500000) scale(-1, 1) translate(-20.000000, -46.500000) "></path>
  53. </g>
  54. </g>
  55. </g>
  56. </svg>''';
  57. final Uint8List svgBytes = utf8.encode(svgStr) as Uint8List;
  58. testWidgets('SvgPicture can work with a FittedBox',
  59. (WidgetTester tester) async {
  60. final GlobalKey key = GlobalKey();
  61. await tester.pumpWidget(
  62. MediaQuery(
  63. data: const MediaQueryData(size: Size(100, 100)),
  64. child: Row(
  65. key: key,
  66. textDirection: TextDirection.ltr,
  67. children: <Widget>[
  68. Flexible(
  69. child: FittedBox(
  70. fit: BoxFit.fitWidth,
  71. child: SvgPicture.string(
  72. svgStr,
  73. width: 20.0,
  74. height: 14.0,
  75. ),
  76. ),
  77. ),
  78. ],
  79. ),
  80. ),
  81. );
  82. await tester.pumpAndSettle();
  83. final Finder widgetFinder = find.byKey(key);
  84. expect(widgetFinder, findsOneWidget);
  85. });
  86. testWidgets('SvgPicture.string', (WidgetTester tester) async {
  87. final GlobalKey key = GlobalKey();
  88. await tester.pumpWidget(
  89. MediaQuery(
  90. data: MediaQueryData.fromWindow(window),
  91. child: RepaintBoundary(
  92. key: key,
  93. child: SvgPicture.string(
  94. svgStr,
  95. width: 100.0,
  96. height: 100.0,
  97. ),
  98. ),
  99. ),
  100. );
  101. await tester.pumpAndSettle();
  102. await _checkWidgetAndGolden(key, 'flutter_logo.string.png');
  103. });
  104. testWidgets('SvgPicture natural size', (WidgetTester tester) async {
  105. final GlobalKey key = GlobalKey();
  106. await tester.pumpWidget(
  107. MediaQuery(
  108. data: MediaQueryData.fromWindow(window),
  109. child: Center(
  110. key: key,
  111. child: SvgPicture.string(
  112. svgStr,
  113. ),
  114. ),
  115. ),
  116. );
  117. await tester.pumpAndSettle();
  118. await _checkWidgetAndGolden(key, 'flutter_logo.natural.png');
  119. });
  120. testWidgets('SvgPicture clipped', (WidgetTester tester) async {
  121. final GlobalKey key = GlobalKey();
  122. await tester.pumpWidget(
  123. MediaQuery(
  124. data: MediaQueryData.fromWindow(window),
  125. child: Center(
  126. key: key,
  127. child: SvgPicture.string(
  128. stickFigureSvgStr,
  129. ),
  130. ),
  131. ),
  132. );
  133. await tester.pumpAndSettle();
  134. await _checkWidgetAndGolden(key, 'stick_figure.withclipping.png');
  135. });
  136. testWidgets('SvgPicture.string rtl', (WidgetTester tester) async {
  137. final GlobalKey key = GlobalKey();
  138. await tester.pumpWidget(
  139. MediaQuery(
  140. data: MediaQueryData.fromWindow(window),
  141. child: RepaintBoundary(
  142. key: key,
  143. child: Directionality(
  144. textDirection: TextDirection.rtl,
  145. child: SvgPicture.string(
  146. svgStr,
  147. matchTextDirection: true,
  148. width: 100.0,
  149. height: 100.0,
  150. ),
  151. ),
  152. ),
  153. ),
  154. );
  155. await tester.pumpAndSettle();
  156. await _checkWidgetAndGolden(key, 'flutter_logo.string.rtl.png');
  157. });
  158. testWidgets('SvgPicture.memory', (WidgetTester tester) async {
  159. final GlobalKey key = GlobalKey();
  160. await tester.pumpWidget(
  161. MediaQuery(
  162. data: MediaQueryData.fromWindow(window),
  163. child: RepaintBoundary(
  164. key: key,
  165. child: SvgPicture.memory(
  166. svgBytes,
  167. ),
  168. ),
  169. ),
  170. );
  171. await tester.pumpAndSettle();
  172. await _checkWidgetAndGolden(key, 'flutter_logo.memory.png');
  173. });
  174. testWidgets('SvgPicture.asset', (WidgetTester tester) async {
  175. final MockAssetBundle mockAsset = MockAssetBundle();
  176. when(mockAsset.loadString('test.svg'))
  177. .thenAnswer((_) => Future<String>.value(svgStr));
  178. final GlobalKey key = GlobalKey();
  179. await tester.pumpWidget(
  180. MediaQuery(
  181. data: MediaQueryData.fromWindow(window),
  182. child: RepaintBoundary(
  183. key: key,
  184. child: SvgPicture.asset(
  185. 'test.svg',
  186. bundle: mockAsset,
  187. ),
  188. ),
  189. ),
  190. );
  191. await tester.pumpAndSettle();
  192. await _checkWidgetAndGolden(key, 'flutter_logo.asset.png');
  193. });
  194. testWidgets('SvgPicture.asset DefaultAssetBundle',
  195. (WidgetTester tester) async {
  196. final MockAssetBundle mockAsset = MockAssetBundle();
  197. when(mockAsset.loadString('test.svg'))
  198. .thenAnswer((_) => Future<String>.value(svgStr));
  199. final GlobalKey key = GlobalKey();
  200. await tester.pumpWidget(
  201. Directionality(
  202. textDirection: TextDirection.ltr,
  203. child: MediaQuery(
  204. data: MediaQueryData.fromWindow(window),
  205. child: DefaultAssetBundle(
  206. bundle: mockAsset,
  207. child: RepaintBoundary(
  208. key: key,
  209. child: SvgPicture.asset(
  210. 'test.svg',
  211. semanticsLabel: 'Test SVG',
  212. ),
  213. ),
  214. ),
  215. ),
  216. ),
  217. );
  218. await tester.pumpAndSettle();
  219. await _checkWidgetAndGolden(key, 'flutter_logo.asset.png');
  220. });
  221. final MockHttpClient mockHttpClient = MockHttpClient();
  222. final MockHttpClientRequest mockRequest = MockHttpClientRequest();
  223. final MockHttpClientResponse mockResponse = MockHttpClientResponse();
  224. when(mockHttpClient.getUrl(any))
  225. .thenAnswer((_) => Future<MockHttpClientRequest>.value(mockRequest));
  226. when(mockRequest.close())
  227. .thenAnswer((_) => Future<MockHttpClientResponse>.value(mockResponse));
  228. when(mockResponse.transform<Uint8List>(any))
  229. .thenAnswer((_) => Stream<Uint8List>.fromIterable(<Uint8List>[svgBytes]));
  230. when(mockResponse.listen(any,
  231. onDone: anyNamed('onDone'),
  232. onError: anyNamed('onError'),
  233. cancelOnError: anyNamed('cancelOnError')))
  234. .thenAnswer((Invocation invocation) {
  235. final void Function(Uint8List) onData =
  236. invocation.positionalArguments[0] as void Function(Uint8List);
  237. final void Function(Object) onError =
  238. invocation.namedArguments[#onError] as void Function(Object);
  239. final VoidCallback onDone =
  240. invocation.namedArguments[#onDone] as VoidCallback;
  241. final bool cancelOnError =
  242. invocation.namedArguments[#cancelOnError] as bool;
  243. return Stream<Uint8List>.fromIterable(<Uint8List>[svgBytes]).listen(
  244. onData,
  245. onDone: onDone,
  246. onError: onError,
  247. cancelOnError: cancelOnError,
  248. );
  249. });
  250. testWidgets('SvgPicture.network', (WidgetTester tester) async {
  251. await HttpOverrides.runZoned(() async {
  252. when(mockResponse.statusCode).thenReturn(200);
  253. final GlobalKey key = GlobalKey();
  254. await tester.pumpWidget(
  255. MediaQuery(
  256. data: MediaQueryData.fromWindow(window),
  257. child: RepaintBoundary(
  258. key: key,
  259. child: SvgPicture.network(
  260. 'test.svg',
  261. ),
  262. ),
  263. ),
  264. );
  265. await tester.pumpAndSettle();
  266. await _checkWidgetAndGolden(key, 'flutter_logo.network.png');
  267. }, createHttpClient: (SecurityContext c) => mockHttpClient);
  268. });
  269. testWidgets('SvgPicture can be created without a MediaQuery',
  270. (WidgetTester tester) async {
  271. final GlobalKey key = GlobalKey();
  272. await tester.pumpWidget(
  273. RepaintBoundary(
  274. key: key,
  275. child: SvgPicture.string(
  276. svgStr,
  277. width: 100.0,
  278. height: 100.0,
  279. ),
  280. ),
  281. );
  282. await tester.pumpAndSettle();
  283. await _checkWidgetAndGolden(key, 'flutter_logo.string.png');
  284. });
  285. testWidgets('SvgPicture.network HTTP exception', (WidgetTester tester) async {
  286. await HttpOverrides.runZoned(() async {
  287. expect(() async {
  288. when(mockResponse.statusCode).thenReturn(400);
  289. await tester.pumpWidget(
  290. MediaQuery(
  291. data: MediaQueryData.fromWindow(window),
  292. child: SvgPicture.network(
  293. 'notFound.svg',
  294. ),
  295. ),
  296. );
  297. }, isNotNull);
  298. }, createHttpClient: (SecurityContext c) => mockHttpClient);
  299. });
  300. testWidgets('SvgPicture semantics', (WidgetTester tester) async {
  301. await tester.pumpWidget(
  302. Directionality(
  303. textDirection: TextDirection.ltr,
  304. child: RepaintBoundary(
  305. child: SvgPicture.string(
  306. svgStr,
  307. semanticsLabel: 'Flutter Logo',
  308. width: 100.0,
  309. height: 100.0,
  310. ),
  311. ),
  312. ),
  313. );
  314. await tester.pumpAndSettle();
  315. expect(find.byType(Semantics), findsOneWidget);
  316. expect(find.bySemanticsLabel('Flutter Logo'), findsOneWidget);
  317. }, semanticsEnabled: true);
  318. testWidgets('SvgPicture semantics - no label', (WidgetTester tester) async {
  319. await tester.pumpWidget(
  320. Directionality(
  321. textDirection: TextDirection.ltr,
  322. child: RepaintBoundary(
  323. child: SvgPicture.string(
  324. svgStr,
  325. width: 100.0,
  326. height: 100.0,
  327. ),
  328. ),
  329. ),
  330. );
  331. await tester.pumpAndSettle();
  332. expect(find.byType(Semantics), findsOneWidget);
  333. }, semanticsEnabled: true);
  334. testWidgets('SvgPicture semantics - exclude', (WidgetTester tester) async {
  335. await tester.pumpWidget(
  336. Directionality(
  337. textDirection: TextDirection.ltr,
  338. child: RepaintBoundary(
  339. child: SvgPicture.string(
  340. svgStr,
  341. excludeFromSemantics: true,
  342. width: 100.0,
  343. height: 100.0,
  344. ),
  345. ),
  346. ),
  347. );
  348. await tester.pumpAndSettle();
  349. expect(find.byType(Semantics), findsNothing);
  350. }, semanticsEnabled: true);
  351. testWidgets('SvgPicture colorFilter - flutter logo',
  352. (WidgetTester tester) async {
  353. final GlobalKey key = GlobalKey();
  354. await tester.pumpWidget(
  355. RepaintBoundary(
  356. key: key,
  357. child: SvgPicture.string(
  358. svgStr,
  359. width: 100.0,
  360. height: 100.0,
  361. color: const Color(0xFF990000),
  362. ),
  363. ),
  364. );
  365. await tester.pumpAndSettle();
  366. await _checkWidgetAndGolden(key, 'flutter_logo.string.color_filter.png');
  367. });
  368. testWidgets('SvgPicture colorFilter with text', (WidgetTester tester) async {
  369. const String svgData =
  370. '''<svg font-family="arial" font-size="14" height="160" width="88" xmlns="http://www.w3.org/2000/svg">
  371. <g stroke="#000" stroke-linecap="round" stroke-width="2" stroke-opacity="1" fill-opacity="1" stroke-linejoin="miter">
  372. <g>
  373. <line x1="60" x2="88" y1="136" y2="136"/>
  374. </g>
  375. <g>
  376. <text stroke-width="1" x="9" y="28">2</text>
  377. </g>
  378. <g>
  379. <text stroke-width="1" x="73" y="156">1</text>
  380. </g>
  381. </g>
  382. </svg>''';
  383. final GlobalKey key = GlobalKey();
  384. await tester.pumpWidget(
  385. RepaintBoundary(
  386. key: key,
  387. child: SvgPicture.string(
  388. svgData,
  389. width: 100.0,
  390. height: 100.0,
  391. color: const Color(0xFF990000),
  392. ),
  393. ),
  394. );
  395. await tester.pumpAndSettle();
  396. await _checkWidgetAndGolden(key, 'text_color_filter.png');
  397. }, skip: !isLinux);
  398. testWidgets('Nested SVG elements report a FlutterError',
  399. (WidgetTester tester) async {
  400. await svg.fromSvgString(
  401. '<svg viewBox="0 0 166 202"><svg viewBox="0 0 166 202"></svg></svg>',
  402. 'test');
  403. final UnsupportedError error = tester.takeException() as UnsupportedError;
  404. expect(error.message, 'Unsupported nested <svg> element.');
  405. });
  406. testWidgets('Can take AlignmentDirectional', (WidgetTester tester) async {
  407. await tester.pumpWidget(Directionality(
  408. textDirection: TextDirection.ltr,
  409. child: SvgPicture.string(
  410. svgStr,
  411. alignment: AlignmentDirectional.bottomEnd,
  412. ),
  413. ));
  414. expect(find.byType(SvgPicture), findsOneWidget);
  415. });
  416. testWidgets('SvgPicture.string respects clipBehavior',
  417. (WidgetTester tester) async {
  418. await tester.pumpWidget(Directionality(
  419. textDirection: TextDirection.ltr,
  420. child: SvgPicture.string(svgStr),
  421. ));
  422. await tester.pumpAndSettle();
  423. // Check that the render object has received the default clip behavior.
  424. final RenderFittedBox renderObject =
  425. tester.allRenderObjects.whereType<RenderFittedBox>().first;
  426. expect(renderObject.clipBehavior, equals(Clip.hardEdge));
  427. // Pump a new widget to check that the render object can update its clip
  428. // behavior.
  429. await tester.pumpWidget(
  430. Directionality(
  431. textDirection: TextDirection.ltr,
  432. child: SvgPicture.string(svgStr, clipBehavior: Clip.antiAlias),
  433. ),
  434. );
  435. await tester.pumpAndSettle();
  436. expect(renderObject.clipBehavior, equals(Clip.antiAlias));
  437. });
  438. testWidgets('SvgPicture.asset respects clipBehavior',
  439. (WidgetTester tester) async {
  440. final MockAssetBundle mockAsset = MockAssetBundle();
  441. when(mockAsset.loadString('test.svg'))
  442. .thenAnswer((_) => Future<String>.value(svgStr));
  443. await tester.pumpWidget(Directionality(
  444. textDirection: TextDirection.ltr,
  445. child: SvgPicture.asset(
  446. 'test.svg',
  447. bundle: mockAsset,
  448. ),
  449. ));
  450. await tester.pumpAndSettle();
  451. // Check that the render object has received the default clip behavior.
  452. final RenderFittedBox renderObject =
  453. tester.allRenderObjects.whereType<RenderFittedBox>().first;
  454. expect(renderObject.clipBehavior, equals(Clip.hardEdge));
  455. // Pump a new widget to check that the render object can update its clip
  456. // behavior.
  457. await tester.pumpWidget(
  458. Directionality(
  459. textDirection: TextDirection.ltr,
  460. child: SvgPicture.asset(
  461. 'test.svg',
  462. bundle: mockAsset,
  463. clipBehavior: Clip.antiAlias,
  464. ),
  465. ),
  466. );
  467. await tester.pumpAndSettle();
  468. expect(renderObject.clipBehavior, equals(Clip.antiAlias));
  469. });
  470. testWidgets('SvgPicture.memory respects clipBehavior',
  471. (WidgetTester tester) async {
  472. await tester.pumpWidget(Directionality(
  473. textDirection: TextDirection.ltr,
  474. child: SvgPicture.memory(svgBytes),
  475. ));
  476. await tester.pumpAndSettle();
  477. // Check that the render object has received the default clip behavior.
  478. final RenderFittedBox renderObject =
  479. tester.allRenderObjects.whereType<RenderFittedBox>().first;
  480. expect(renderObject.clipBehavior, equals(Clip.hardEdge));
  481. // Pump a new widget to check that the render object can update its clip
  482. // behavior.
  483. await tester.pumpWidget(
  484. Directionality(
  485. textDirection: TextDirection.ltr,
  486. child: SvgPicture.memory(svgBytes, clipBehavior: Clip.antiAlias),
  487. ),
  488. );
  489. await tester.pumpAndSettle();
  490. expect(renderObject.clipBehavior, equals(Clip.antiAlias));
  491. });
  492. testWidgets('SvgPicture.network respects clipBehavior',
  493. (WidgetTester tester) async {
  494. await HttpOverrides.runZoned(() async {
  495. when(mockResponse.statusCode).thenReturn(200);
  496. await tester.pumpWidget(
  497. Directionality(
  498. textDirection: TextDirection.ltr,
  499. child: SvgPicture.network('test.svg'),
  500. ),
  501. );
  502. await tester.pumpAndSettle();
  503. // Check that the render object has received the default clip behavior.
  504. final RenderFittedBox renderObject =
  505. tester.allRenderObjects.whereType<RenderFittedBox>().first;
  506. expect(renderObject.clipBehavior, equals(Clip.hardEdge));
  507. // Pump a new widget to check that the render object can update its clip
  508. // behavior.
  509. await tester.pumpWidget(
  510. Directionality(
  511. textDirection: TextDirection.ltr,
  512. child: SvgPicture.network('test.svg', clipBehavior: Clip.antiAlias),
  513. ),
  514. );
  515. await tester.pumpAndSettle();
  516. expect(renderObject.clipBehavior, equals(Clip.antiAlias));
  517. }, createHttpClient: (SecurityContext c) => mockHttpClient);
  518. });
  519. testWidgets('SvgPicture respects clipBehavior', (WidgetTester tester) async {
  520. await tester.pumpWidget(Directionality(
  521. textDirection: TextDirection.ltr,
  522. child: SvgPicture.string(svgStr),
  523. ));
  524. await tester.pumpAndSettle();
  525. // Check that the render object has received the default clip behavior.
  526. final RenderFittedBox renderObject =
  527. tester.allRenderObjects.whereType<RenderFittedBox>().first;
  528. expect(renderObject.clipBehavior, equals(Clip.hardEdge));
  529. // Pump a new widget to check that the render object can update its clip
  530. // behavior.
  531. await tester.pumpWidget(
  532. Directionality(
  533. textDirection: TextDirection.ltr,
  534. child: SvgPicture.string(svgStr, clipBehavior: Clip.antiAlias),
  535. ),
  536. );
  537. await tester.pumpAndSettle();
  538. expect(renderObject.clipBehavior, equals(Clip.antiAlias));
  539. });
  540. }
  541. class MockAssetBundle extends Mock implements AssetBundle {}
  542. class MockHttpClient extends Mock implements HttpClient {}
  543. class MockHttpClientRequest extends Mock implements HttpClientRequest {}
  544. class MockHttpClientResponse extends Mock implements HttpClientResponse {}