Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into add_trace_to_when_s…
Browse files Browse the repository at this point in the history
…tateError
  • Loading branch information
tomconnell-wf committed Jun 12, 2024
2 parents 711ff3c + c88c416 commit 89d430e
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 34 deletions.
2 changes: 2 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ linter:
- no_leading_underscores_for_local_identifiers
- no_logic_in_create_state
- no_runtimeType_toString
- no_wildcard_variable_uses
- non_constant_identifier_names
- noop_primitive_operations
- null_check_on_nullable_type_parameter
Expand Down Expand Up @@ -153,6 +154,7 @@ linter:
- tighten_type_of_initializing_formals
- type_annotate_public_apis
- type_init_formals
- type_literal_in_constant_pattern
- unawaited_futures
- unnecessary_await_in_return
- unnecessary_brace_in_string_interps
Expand Down
5 changes: 5 additions & 0 deletions packages/mocktail/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 1.0.4

- refactor: upgrade `analysis_options.yaml` ([#242](https://github.com/felangel/mocktail/issues/242))
- chore: fix minor typo in `CHANGELOG.md`

# 1.0.3

- docs: update ` README.md` to include `any(that: ...)` ([#226](https://github.com/felangel/mocktail/issues/226))
Expand Down
32 changes: 16 additions & 16 deletions packages/mocktail/lib/src/mocktail.dart
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,11 @@ When<T> Function<T>(T Function() x) get when {
whenCall: _whenCall);
}
_whenInProgress = true;
return <T>(T Function() _) {
return <T>(T Function() fn) {
try {
_();
} catch (_) {
if (_ is! TypeError) rethrow;
fn();
} catch (e) {
if (e is! TypeError) rethrow;
}
_whenInProgress = false;
return When<T>();
Expand Down Expand Up @@ -418,12 +418,12 @@ List<VerificationResult> Function<T>(
throw StateError(_verifyCalls.join());
}
_verificationInProgress = true;
return <T>(List<T Function()> _) {
for (final invocation in _) {
return <T>(List<T Function()> invocations) {
for (final invocation in invocations) {
try {
invocation();
} catch (_) {
if (_ is! TypeError) rethrow;
} catch (e) {
if (e is! TypeError) rethrow;
}
}

Expand Down Expand Up @@ -526,11 +526,11 @@ Verify _makeVerify(bool never) {
);
}
_verificationInProgress = true;
return <T>(T Function() mock) {
return <T>(T Function() fn) {
try {
mock();
} catch (_) {
if (_ is! TypeError) rethrow;
fn();
} catch (e) {
if (e is! TypeError) rethrow;
}
_verificationInProgress = false;
if (_verifyCalls.length == 1) {
Expand Down Expand Up @@ -781,11 +781,11 @@ class _VerifyCall {
/// future will return immediately.
Future<Invocation> Function<T>(T Function() _) get untilCalled {
_untilCalledInProgress = true;
return <T>(T Function() _) {
return <T>(T Function() fn) {
try {
_();
} catch (_) {
if (_ is! TypeError) rethrow;
fn();
} catch (e) {
if (e is! TypeError) rethrow;
}
_untilCalledInProgress = false;
return _untilCall!.invocationFuture;
Expand Down
2 changes: 1 addition & 1 deletion packages/mocktail/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: mocktail
description: A Dart mock library which simplifies mocking with null safety support and no manual mocks or code generation.
version: 1.0.3
version: 1.0.4
repository: https://github.com/felangel/mocktail
homepage: https://github.com/felangel/mocktail/tree/main/packages/mocktail
topics: [mock, test]
Expand Down
6 changes: 6 additions & 0 deletions packages/mocktail_image_network/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 1.2.0

- feat: add customizable `imageResolver` to `mockNetworkImages` ([#232](https://github.com/felangel/mocktail/issues/232))
- fix: svg support ([#232](https://github.com/felangel/mocktail/issues/232))
- chore: include `package:flutter_svg` in example

# 1.1.0

- feat: add customizable `imageBytes` to `mockNetworkImages` ([#214](https://github.com/felangel/mocktail/issues/214))
Expand Down
15 changes: 12 additions & 3 deletions packages/mocktail_image_network/example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';

/// {@template fake_app}
/// Sample app used to showcase `mocktail_image_network`
Expand All @@ -12,9 +13,17 @@ class FakeApp extends StatelessWidget {
return MaterialApp(
home: Scaffold(
body: Center(
child: Image.network(
// URL to the Flutter logo from https://flutter.dev/brand
'https://storage.googleapis.com/cms-storage-bucket/c823e53b3a1a7b0d36a9.png',
child: Column(
children: [
Image.network(
// URL to the png Flutter logo from https://flutter.dev/brand
'https://storage.googleapis.com/cms-storage-bucket/c823e53b3a1a7b0d36a9.png',
),
SvgPicture.network(
// URL to the svg Flutter logo from https://flutter.dev/brand
'https://storage.googleapis.com/cms-storage-bucket/847ae81f5430402216fd.svg',
),
],
),
),
),
Expand Down
1 change: 1 addition & 0 deletions packages/mocktail_image_network/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ environment:
dependencies:
flutter:
sdk: flutter
flutter_svg: ^2.0.0

dev_dependencies:
flutter_test:
Expand Down
2 changes: 2 additions & 0 deletions packages/mocktail_image_network/example/test/image_test.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import 'package:example/main.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail_image_network/mocktail_image_network.dart';

void main() {
testWidgets('can use mocktail for network images', (tester) async {
await mockNetworkImages(() async => tester.pumpWidget(const FakeApp()));
expect(find.byType(Image), findsOneWidget);
expect(find.byType(SvgPicture), findsOneWidget);
});
}
101 changes: 88 additions & 13 deletions packages/mocktail_image_network/lib/src/mocktail_image_network.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:mocktail/mocktail.dart';

/// Signature for a function that returns a `List<int>` for a given [Uri].
typedef ImageResolver = List<int> Function(Uri uri);

/// {@template mocktail_image_network}
/// Utility method that allows you to execute a widget test when you pump a
/// widget that contains an `Image.network` node in its tree.
Expand Down Expand Up @@ -40,11 +44,19 @@ import 'package:mocktail/mocktail.dart';
/// }
/// ```
/// {@endtemplate}
T mockNetworkImages<T>(T Function() body, {Uint8List? imageBytes}) {
T mockNetworkImages<T>(
T Function() body, {
Uint8List? imageBytes,
ImageResolver? imageResolver,
}) {
assert(
imageBytes == null || imageResolver == null,
'One of imageBytes or imageResolver can be provided, but not both.',
);
return HttpOverrides.runZoned(
body,
createHttpClient: (_) => _createHttpClient(
data: imageBytes ?? _transparentPixelPng,
imageResolver ??= _defaultImageResolver(imageBytes),
),
);
}
Expand All @@ -53,6 +65,7 @@ class _MockHttpClient extends Mock implements HttpClient {
_MockHttpClient() {
registerFallbackValue((List<int> _) {});
registerFallbackValue(Uri());
registerFallbackValue(const Stream<List<int>>.empty());
}
}

Expand All @@ -62,15 +75,64 @@ class _MockHttpClientResponse extends Mock implements HttpClientResponse {}

class _MockHttpHeaders extends Mock implements HttpHeaders {}

HttpClient _createHttpClient({required List<int> data}) {
HttpClient _createHttpClient(ImageResolver imageResolver) {
final client = _MockHttpClient();

when(() => client.getUrl(any())).thenAnswer(
(invokation) async => _createRequest(
invokation.positionalArguments.first as Uri,
imageResolver,
),
);
when(() => client.openUrl(any(), any())).thenAnswer(
(invokation) async => _createRequest(
invokation.positionalArguments.last as Uri,
imageResolver,
),
);

return client;
}

HttpClientRequest _createRequest(Uri uri, ImageResolver imageResolver) {
final request = _MockHttpClientRequest();
final headers = _MockHttpHeaders();

when(() => request.headers).thenReturn(headers);
when(
() => request.addStream(any()),
).thenAnswer((invocation) {
final stream = invocation.positionalArguments.first as Stream<List<int>>;
return stream.fold<List<int>>(
<int>[],
(previous, element) => previous..addAll(element),
);
});
when(
request.close,
).thenAnswer((_) async => _createResponse(uri, imageResolver));

return request;
}

HttpClientResponse _createResponse(Uri uri, ImageResolver imageResolver) {
final response = _MockHttpClientResponse();
final headers = _MockHttpHeaders();
when(() => response.compressionState)
.thenReturn(HttpClientResponseCompressionState.notCompressed);
when(() => response.contentLength).thenReturn(_transparentPixelPng.length);
final data = imageResolver(uri);

when(() => response.headers).thenReturn(headers);
when(() => response.contentLength).thenReturn(data.length);
when(() => response.statusCode).thenReturn(HttpStatus.ok);
when(() => response.isRedirect).thenReturn(false);
when(() => response.redirects).thenReturn([]);
when(() => response.persistentConnection).thenReturn(false);
when(() => response.reasonPhrase).thenReturn('OK');
when(
() => response.compressionState,
).thenReturn(HttpClientResponseCompressionState.notCompressed);
when(
() => response.handleError(any(), test: any(named: 'test')),
).thenAnswer((_) => Stream<List<int>>.value(data));
when(
() => response.listen(
any(),
Expand All @@ -80,17 +142,30 @@ HttpClient _createHttpClient({required List<int> data}) {
),
).thenAnswer((invocation) {
final onData =
invocation.positionalArguments[0] as void Function(List<int>);
invocation.positionalArguments.first as void Function(List<int>);
final onDone = invocation.namedArguments[#onDone] as void Function()?;
return Stream<List<int>>.fromIterable(<List<int>>[data])
.listen(onData, onDone: onDone);
return Stream<List<int>>.fromIterable(
<List<int>>[data],
).listen(onData, onDone: onDone);
});
when(() => request.headers).thenReturn(headers);
when(request.close).thenAnswer((_) async => response);
when(() => client.getUrl(any())).thenAnswer((_) async => request);
return client;
return response;
}

ImageResolver _defaultImageResolver(Uint8List? imageBytes) {
if (imageBytes != null) return (_) => imageBytes;

return (uri) {
final extension = uri.path.split('.').last;
return _mockedResponses[extension] ?? _transparentPixelPng;
};
}

final _mockedResponses = <String, List<int>>{
'png': _transparentPixelPng,
'svg': _emptySvg,
};

final _emptySvg = '<svg viewBox="0 0 10 10" />'.codeUnits;
final _transparentPixelPng = base64Decode(
'''iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==''',
);
2 changes: 1 addition & 1 deletion packages/mocktail_image_network/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: mocktail_image_network
description: A Dart package which allows you to mock Image.network in your widget tests with confidence using the mocktail package.
version: 1.1.0
version: 1.2.0
repository: https://github.com/felangel/mocktail
homepage: https://github.com/felangel/mocktail/tree/main/packages/mocktail_image_network
topics: [mock, test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,72 @@ void main() {
imageBytes: greenPixel,
);
});

test('should properly mock svg response', () async {
await mockNetworkImages(() async {
final expectedData = '<svg viewBox="0 0 10 10" />'.codeUnits;
final client = HttpClient()..autoUncompress = false;
final request = await client.openUrl(
'GET',
Uri.https('', '/image.svg'),
);
await request.addStream(Stream.value(<int>[]));
final response = await request.close();
final data = <int>[];

response.listen(data.addAll);

// Wait for all microtasks to run
await Future<void>.delayed(Duration.zero);

expect(response.redirects, isEmpty);
expect(data, equals(expectedData));
});
});

test('should properly use custom imageResolver', () async {
final bluePixel = base64Decode(
'''iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYPj/HwADAgH/eL9GtQAAAABJRU5ErkJggg==''',
);

await mockNetworkImages(
() async {
final client = HttpClient()..autoUncompress = false;
final request = await client.getUrl(Uri.https(''));
final response = await request.close();
final data = <int>[];

response.listen(data.addAll);

// Wait for all microtasks to run
await Future<void>.delayed(Duration.zero);

expect(data, equals(bluePixel));
},
imageResolver: (_) => bluePixel,
);
});

test(
'should throw assertion error '
'when both imageBytes and imageResolver are used.', () async {
final bluePixel = base64Decode(
'''iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NgYPj/HwADAgH/eL9GtQAAAABJRU5ErkJggg==''',
);
expect(
() => mockNetworkImages(
() {},
imageBytes: bluePixel,
imageResolver: (_) => bluePixel,
),
throwsA(
isA<AssertionError>().having(
(e) => e.message,
'message',
'One of imageBytes or imageResolver can be provided, but not both.',
),
),
);
});
});
}

0 comments on commit 89d430e

Please sign in to comment.