import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:clide/src/svg/svg_document.dart'; import 'package:clide/widgets/src/svg/svg_painter.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; /// Pixel-probe tests: render the scene to a `ui.Image` and read back colours. /// Deterministic for fills, independent of fonts/goldens. void main() { TestWidgetsFlutterBinding.ensureInitialized(); Future render(String svg, double w, double h, {SvgImageResolver? images}) async { final recorder = ui.PictureRecorder(); final canvas = ui.Canvas(recorder, Rect.fromLTWH(0, 0, w, h)); paintSvg(canvas, Size(w, h), buildSvgDocument(svg), images: images); return recorder.endRecording().toImage(w.round(), h.round()); } Future solidImage(int w, int h, int argb) { final px = Uint8List(w * h * 4); for (var i = 0; i < w * h; i++) { px[i * 4] = (argb >> 16) & 0xFF; px[i * 4 + 1] = (argb >> 8) & 0xFF; px[i * 4 + 2] = argb & 0xFF; px[i * 4 + 3] = (argb >> 24) & 0xFF; } final c = Completer(); ui.decodeImageFromPixels(px, w, h, ui.PixelFormat.rgba8888, c.complete); return c.future; } Future argbAt(ui.Image img, int x, int y) async { final data = (await img.toByteData())!; final i = (y * img.width + x) * 4; return (data.getUint8(i + 3) << 24) | (data.getUint8(i) << 16) | (data.getUint8(i + 1) << 8) | data.getUint8(i + 2); } int alpha(int argb) => (argb >> 24) & 0xFF; test('fills a rect with its colour', () async { final img = await render('', 10, 10); expect(await argbAt(img, 5, 5), 0xFFFF0000); }); test('default fill is black', () async { final img = await render('', 10, 10); expect(await argbAt(img, 5, 5), 0xFF000000); }); test('fill="none" leaves the interior transparent', () async { final img = await render('', 10, 10); expect(alpha(await argbAt(img, 5, 5)), 0); }); test('circle centre is filled, corner is empty', () async { final img = await render('', 10, 10); expect(await argbAt(img, 5, 5), 0xFF0000FF); expect(alpha(await argbAt(img, 0, 0)), 0); }); test('viewBox scales up to the paint size', () async { final img = await render('', 20, 20); expect(await argbAt(img, 10, 10), 0xFFFF0000); }); test('transform translates the shape', () async { final img = await render('', 20, 20); expect(await argbAt(img, 12, 12), 0xFFFF0000); expect(alpha(await argbAt(img, 2, 2)), 0); }); test('group opacity dims the contents', () async { final img = await render('', 10, 10); expect(alpha(await argbAt(img, 5, 5)), closeTo(128, 4)); }); test('draws a marker-end arrowhead, rotated to the path direction', () async { // A rightward path with a green right-pointing triangle marker at its end. const svg = '' '' '' ''; final data = (await (await render(svg, 40, 20)).toByteData())!; var greenDrawn = false; for (var i = 0; i < data.lengthInBytes; i += 4) { final r = data.getUint8(i), g = data.getUint8(i + 1), b = data.getUint8(i + 2), a = data.getUint8(i + 3); if (a > 200 && g > 200 && r < 80 && b < 80) { greenDrawn = true; break; } } expect(greenDrawn, isTrue, reason: 'the green arrowhead should have been painted'); }); test('paints an via the injected resolver, into its dest rect', () async { final pic = await solidImage(4, 4, 0xFFFF00FF); const svg = ''; final img = await render(svg, 10, 10, images: (href) => href == 'pic' ? pic : null); expect(await argbAt(img, 5, 5), 0xFFFF00FF); }); test('an with no resolver paints nothing', () async { const svg = ''; final img = await render(svg, 10, 10); expect(alpha(await argbAt(img, 5, 5)), 0); }); test('renders the real d2 fixture without error and draws ink', () async { final svg = File('test/svg/fixtures/d2_pipeline.svg').readAsStringSync(); final img = await render(svg, 200, 120); final data = (await img.toByteData())!; var anyInk = false; for (var i = 3; i < data.lengthInBytes; i += 4) { if (data.getUint8(i) != 0) { anyInk = true; break; } } expect(anyInk, isTrue); }); }