1+ import 'dart:async' ;
12import 'dart:convert' ;
23import 'dart:io' ;
34import 'dart:typed_data' ;
45
56import 'package:mocktail/mocktail.dart' ;
67
8+ /// Signature for a function that returns a `List<int>` for a given [Uri] .
9+ typedef ImageResolver = List <int > Function (Uri uri);
10+
711/// {@template mocktail_image_network}
812/// Utility method that allows you to execute a widget test when you pump a
913/// widget that contains an `Image.network` node in its tree.
@@ -40,11 +44,19 @@ import 'package:mocktail/mocktail.dart';
4044/// }
4145/// ```
4246/// {@endtemplate}
43- T mockNetworkImages <T >(T Function () body, {Uint8List ? imageBytes}) {
47+ T mockNetworkImages <T >(
48+ T Function () body, {
49+ Uint8List ? imageBytes,
50+ ImageResolver ? imageResolver,
51+ }) {
52+ assert (
53+ imageBytes == null || imageResolver == null ,
54+ 'One of imageBytes or imageResolver can be provided, but not both.' ,
55+ );
4456 return HttpOverrides .runZoned (
4557 body,
4658 createHttpClient: (_) => _createHttpClient (
47- data : imageBytes ?? _transparentPixelPng ,
59+ imageResolver ?? = _defaultImageResolver (imageBytes) ,
4860 ),
4961 );
5062}
@@ -53,6 +65,7 @@ class _MockHttpClient extends Mock implements HttpClient {
5365 _MockHttpClient () {
5466 registerFallbackValue ((List <int > _) {});
5567 registerFallbackValue (Uri ());
68+ registerFallbackValue (const Stream <List <int >>.empty ());
5669 }
5770}
5871
@@ -62,15 +75,64 @@ class _MockHttpClientResponse extends Mock implements HttpClientResponse {}
6275
6376class _MockHttpHeaders extends Mock implements HttpHeaders {}
6477
65- HttpClient _createHttpClient ({ required List < int > data} ) {
78+ HttpClient _createHttpClient (ImageResolver imageResolver ) {
6679 final client = _MockHttpClient ();
80+
81+ when (() => client.getUrl (any ())).thenAnswer (
82+ (invokation) async => _createRequest (
83+ invokation.positionalArguments.first as Uri ,
84+ imageResolver,
85+ ),
86+ );
87+ when (() => client.openUrl (any (), any ())).thenAnswer (
88+ (invokation) async => _createRequest (
89+ invokation.positionalArguments.last as Uri ,
90+ imageResolver,
91+ ),
92+ );
93+
94+ return client;
95+ }
96+
97+ HttpClientRequest _createRequest (Uri uri, ImageResolver imageResolver) {
6798 final request = _MockHttpClientRequest ();
99+ final headers = _MockHttpHeaders ();
100+
101+ when (() => request.headers).thenReturn (headers);
102+ when (
103+ () => request.addStream (any ()),
104+ ).thenAnswer ((invocation) {
105+ final stream = invocation.positionalArguments.first as Stream <List <int >>;
106+ return stream.fold <List <int >>(
107+ < int > [],
108+ (previous, element) => previous..addAll (element),
109+ );
110+ });
111+ when (
112+ request.close,
113+ ).thenAnswer ((_) async => _createResponse (uri, imageResolver));
114+
115+ return request;
116+ }
117+
118+ HttpClientResponse _createResponse (Uri uri, ImageResolver imageResolver) {
68119 final response = _MockHttpClientResponse ();
69120 final headers = _MockHttpHeaders ();
70- when (() => response.compressionState)
71- .thenReturn (HttpClientResponseCompressionState .notCompressed);
72- when (() => response.contentLength).thenReturn (_transparentPixelPng.length);
121+ final data = imageResolver (uri);
122+
123+ when (() => response.headers).thenReturn (headers);
124+ when (() => response.contentLength).thenReturn (data.length);
73125 when (() => response.statusCode).thenReturn (HttpStatus .ok);
126+ when (() => response.isRedirect).thenReturn (false );
127+ when (() => response.redirects).thenReturn ([]);
128+ when (() => response.persistentConnection).thenReturn (false );
129+ when (() => response.reasonPhrase).thenReturn ('OK' );
130+ when (
131+ () => response.compressionState,
132+ ).thenReturn (HttpClientResponseCompressionState .notCompressed);
133+ when (
134+ () => response.handleError (any (), test: any (named: 'test' )),
135+ ).thenAnswer ((_) => Stream <List <int >>.value (data));
74136 when (
75137 () => response.listen (
76138 any (),
@@ -80,17 +142,30 @@ HttpClient _createHttpClient({required List<int> data}) {
80142 ),
81143 ).thenAnswer ((invocation) {
82144 final onData =
83- invocation.positionalArguments[ 0 ] as void Function (List <int >);
145+ invocation.positionalArguments.first as void Function (List <int >);
84146 final onDone = invocation.namedArguments[#onDone] as void Function ()? ;
85- return Stream <List <int >>.fromIterable (< List <int >> [data])
86- .listen (onData, onDone: onDone);
147+ return Stream <List <int >>.fromIterable (
148+ < List <int >> [data],
149+ ).listen (onData, onDone: onDone);
87150 });
88- when (() => request.headers).thenReturn (headers);
89- when (request.close).thenAnswer ((_) async => response);
90- when (() => client.getUrl (any ())).thenAnswer ((_) async => request);
91- return client;
151+ return response;
92152}
93153
154+ ImageResolver _defaultImageResolver (Uint8List ? imageBytes) {
155+ if (imageBytes != null ) return (_) => imageBytes;
156+
157+ return (uri) {
158+ final extension = uri.path.split ('.' ).last;
159+ return _mockedResponses[extension ] ?? _transparentPixelPng;
160+ };
161+ }
162+
163+ final _mockedResponses = < String , List <int >> {
164+ 'png' : _transparentPixelPng,
165+ 'svg' : _emptySvg,
166+ };
167+
168+ final _emptySvg = '<svg viewBox="0 0 10 10" />' .codeUnits;
94169final _transparentPixelPng = base64Decode (
95170 '''iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==''' ,
96171);
0 commit comments