mirror of
https://github.com/flutter/flutter.git
synced 2026-02-15 23:33:36 +08:00
* Use `curly_braces_in_flow_control_structures` for `widgets` * fix comments * fix comments
383 lines
14 KiB
Dart
383 lines
14 KiB
Dart
// Copyright 2014 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.
|
|
|
|
@TestOn('!chrome')
|
|
import 'dart:typed_data';
|
|
import 'dart:ui' as ui show Image;
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import '../image_data.dart';
|
|
|
|
ByteData testByteData(double scale) => ByteData(8)..setFloat64(0, scale);
|
|
double scaleOf(ByteData data) => data.getFloat64(0);
|
|
|
|
const String testManifest = '''
|
|
{
|
|
"assets/image.png" : [
|
|
"assets/image.png",
|
|
"assets/1.5x/image.png",
|
|
"assets/2.0x/image.png",
|
|
"assets/3.0x/image.png",
|
|
"assets/4.0x/image.png"
|
|
]
|
|
}
|
|
''';
|
|
|
|
class TestAssetBundle extends CachingAssetBundle {
|
|
TestAssetBundle({ this.manifest = testManifest });
|
|
|
|
final String manifest;
|
|
|
|
@override
|
|
Future<ByteData> load(String key) {
|
|
late ByteData data;
|
|
switch (key) {
|
|
case 'assets/image.png':
|
|
data = testByteData(1.0);
|
|
break;
|
|
case 'assets/1.0x/image.png':
|
|
data = testByteData(10.0); // see "...with a main asset and a 1.0x asset"
|
|
break;
|
|
case 'assets/1.5x/image.png':
|
|
data = testByteData(1.5);
|
|
break;
|
|
case 'assets/2.0x/image.png':
|
|
data = testByteData(2.0);
|
|
break;
|
|
case 'assets/3.0x/image.png':
|
|
data = testByteData(3.0);
|
|
break;
|
|
case 'assets/4.0x/image.png':
|
|
data = testByteData(4.0);
|
|
break;
|
|
}
|
|
return SynchronousFuture<ByteData>(data);
|
|
}
|
|
|
|
@override
|
|
Future<String> loadString(String key, { bool cache = true }) {
|
|
if (key == 'AssetManifest.json') {
|
|
return SynchronousFuture<String>(manifest);
|
|
}
|
|
return SynchronousFuture<String>('');
|
|
}
|
|
|
|
@override
|
|
String toString() => '${describeIdentity(this)}()';
|
|
}
|
|
|
|
class FakeImageStreamCompleter extends ImageStreamCompleter {
|
|
FakeImageStreamCompleter(Future<ImageInfo> image) {
|
|
image.then<void>(setImage);
|
|
}
|
|
}
|
|
|
|
class TestAssetImage extends AssetImage {
|
|
const TestAssetImage(super.name, this.images);
|
|
|
|
final Map<double, ui.Image> images;
|
|
|
|
@override
|
|
ImageStreamCompleter load(AssetBundleImageKey key, DecoderCallback decode) {
|
|
late ImageInfo imageInfo;
|
|
key.bundle.load(key.name).then<void>((ByteData data) {
|
|
final ui.Image image = images[scaleOf(data)]!;
|
|
assert(image != null, 'Expected ${scaleOf(data)} to have a key in $images');
|
|
imageInfo = ImageInfo(image: image, scale: key.scale);
|
|
});
|
|
return FakeImageStreamCompleter(
|
|
SynchronousFuture<ImageInfo>(imageInfo),
|
|
);
|
|
}
|
|
}
|
|
|
|
Widget buildImageAtRatio(String imageName, Key key, double ratio, bool inferSize, Map<double, ui.Image> images, [ AssetBundle? bundle ]) {
|
|
const double windowSize = 500.0; // 500 logical pixels
|
|
const double imageSize = 200.0; // 200 logical pixels
|
|
|
|
return MediaQuery(
|
|
data: MediaQueryData(
|
|
size: const Size(windowSize, windowSize),
|
|
devicePixelRatio: ratio,
|
|
),
|
|
child: DefaultAssetBundle(
|
|
bundle: bundle ?? TestAssetBundle(),
|
|
child: Center(
|
|
child: inferSize ?
|
|
Image(
|
|
key: key,
|
|
excludeFromSemantics: true,
|
|
image: TestAssetImage(imageName, images),
|
|
) :
|
|
Image(
|
|
key: key,
|
|
excludeFromSemantics: true,
|
|
image: TestAssetImage(imageName, images),
|
|
height: imageSize,
|
|
width: imageSize,
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildImageCacheResized(String name, Key key, int width, int height, int cacheWidth, int cacheHeight) {
|
|
return Center(
|
|
child: RepaintBoundary(
|
|
child: SizedBox(
|
|
width: 250,
|
|
height: 250,
|
|
child: Center(
|
|
child: Image.memory(
|
|
Uint8List.fromList(kTransparentImage),
|
|
key: key,
|
|
excludeFromSemantics: true,
|
|
color: const Color(0xFF00FFFF),
|
|
colorBlendMode: BlendMode.plus,
|
|
width: width.toDouble(),
|
|
height: height.toDouble(),
|
|
cacheWidth: cacheWidth,
|
|
cacheHeight: cacheHeight,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
RenderImage getRenderImage(WidgetTester tester, Key key) {
|
|
return tester.renderObject<RenderImage>(find.byKey(key));
|
|
}
|
|
|
|
Future<void> pumpTreeToLayout(WidgetTester tester, Widget widget) {
|
|
const Duration pumpDuration = Duration.zero;
|
|
const EnginePhase pumpPhase = EnginePhase.layout;
|
|
return tester.pumpWidget(widget, pumpDuration, pumpPhase);
|
|
}
|
|
|
|
void main() {
|
|
const String image = 'assets/image.png';
|
|
|
|
final Map<double, ui.Image> images = <double, ui.Image>{};
|
|
setUpAll(() async {
|
|
for (final double scale in const <double>[0.5, 1.0, 1.5, 2.0, 4.0, 10.0]) {
|
|
final int dimension = (48 * scale).floor();
|
|
images[scale] = await createTestImage(width: dimension, height: dimension);
|
|
}
|
|
});
|
|
|
|
testWidgets('Image for device pixel ratio 1.0', (WidgetTester tester) async {
|
|
const double ratio = 1.0;
|
|
Key key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images));
|
|
expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
|
|
expect(getRenderImage(tester, key).scale, 1.0);
|
|
key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images));
|
|
expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
|
|
expect(getRenderImage(tester, key).scale, 1.0);
|
|
});
|
|
|
|
testWidgets('Image for device pixel ratio 0.5', (WidgetTester tester) async {
|
|
const double ratio = 0.5;
|
|
Key key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images));
|
|
expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
|
|
expect(getRenderImage(tester, key).scale, 1.0);
|
|
key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images));
|
|
expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
|
|
expect(getRenderImage(tester, key).scale, 1.0);
|
|
});
|
|
|
|
testWidgets('Image for device pixel ratio 1.5', (WidgetTester tester) async {
|
|
const double ratio = 1.5;
|
|
Key key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images));
|
|
expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
|
|
expect(getRenderImage(tester, key).scale, 1.5);
|
|
key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images));
|
|
expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
|
|
expect(getRenderImage(tester, key).scale, 1.5);
|
|
});
|
|
|
|
// A 1.75 DPR screen is typically a low-resolution screen, such that physical
|
|
// pixels are visible to the user. For such screens we prefer to pick the
|
|
// higher resolution image, if available.
|
|
testWidgets('Image for device pixel ratio 1.75', (WidgetTester tester) async {
|
|
const double ratio = 1.75;
|
|
Key key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images));
|
|
expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
|
|
expect(getRenderImage(tester, key).scale, 2.0);
|
|
key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images));
|
|
expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
|
|
expect(getRenderImage(tester, key).scale, 2.0);
|
|
});
|
|
|
|
testWidgets('Image for device pixel ratio 2.3', (WidgetTester tester) async {
|
|
const double ratio = 2.3;
|
|
Key key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images));
|
|
expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
|
|
expect(getRenderImage(tester, key).scale, 2.0);
|
|
key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images));
|
|
expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
|
|
expect(getRenderImage(tester, key).scale, 2.0);
|
|
});
|
|
|
|
testWidgets('Image for device pixel ratio 3.7', (WidgetTester tester) async {
|
|
const double ratio = 3.7;
|
|
Key key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images));
|
|
expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
|
|
expect(getRenderImage(tester, key).scale, 4.0);
|
|
key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images));
|
|
expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
|
|
expect(getRenderImage(tester, key).scale, 4.0);
|
|
});
|
|
|
|
testWidgets('Image for device pixel ratio 5.1', (WidgetTester tester) async {
|
|
const double ratio = 5.1;
|
|
Key key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images));
|
|
expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
|
|
expect(getRenderImage(tester, key).scale, 4.0);
|
|
key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images));
|
|
expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
|
|
expect(getRenderImage(tester, key).scale, 4.0);
|
|
});
|
|
|
|
testWidgets('Image for device pixel ratio 1.0, with no main asset', (WidgetTester tester) async {
|
|
const String manifest = '''
|
|
{
|
|
"assets/image.png" : [
|
|
"assets/1.5x/image.png",
|
|
"assets/2.0x/image.png",
|
|
"assets/3.0x/image.png",
|
|
"assets/4.0x/image.png"
|
|
]
|
|
}
|
|
''';
|
|
final AssetBundle bundle = TestAssetBundle(manifest: manifest);
|
|
|
|
const double ratio = 1.0;
|
|
Key key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images, bundle));
|
|
expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
|
|
expect(getRenderImage(tester, key).scale, 1.5);
|
|
key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images, bundle));
|
|
expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
|
|
expect(getRenderImage(tester, key).scale, 1.5);
|
|
});
|
|
|
|
testWidgets('Image for device pixel ratio 1.0, with a main asset and a 1.0x asset', (WidgetTester tester) async {
|
|
// If both a main asset and a 1.0x asset are specified, then prefer
|
|
// the 1.0x asset.
|
|
|
|
const String manifest = '''
|
|
{
|
|
"assets/image.png" : [
|
|
"assets/image.png",
|
|
"assets/1.0x/image.png",
|
|
"assets/1.5x/image.png",
|
|
"assets/2.0x/image.png",
|
|
"assets/3.0x/image.png",
|
|
"assets/4.0x/image.png"
|
|
]
|
|
}
|
|
''';
|
|
final AssetBundle bundle = TestAssetBundle(manifest: manifest);
|
|
|
|
const double ratio = 1.0;
|
|
Key key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images, bundle));
|
|
expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
|
|
// Verify we got the 10x scaled image, since the test ByteData said it should be 10x.
|
|
expect(getRenderImage(tester, key).image!.height, 480);
|
|
key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images, bundle));
|
|
expect(getRenderImage(tester, key).size, const Size(480.0, 480.0));
|
|
// Verify we got the 10x scaled image, since the test ByteData said it should be 10x.
|
|
expect(getRenderImage(tester, key).image!.height, 480);
|
|
});
|
|
|
|
testWidgets('Image cache resize upscale display 5', (WidgetTester tester) async {
|
|
final Key key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageCacheResized(image, key, 5, 5, 20, 20));
|
|
expect(getRenderImage(tester, key).size, const Size(5.0, 5.0));
|
|
});
|
|
|
|
testWidgets('Image cache resize upscale display 50', (WidgetTester tester) async {
|
|
final Key key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageCacheResized(image, key, 50, 50, 20, 20));
|
|
expect(getRenderImage(tester, key).size, const Size(50.0, 50.0));
|
|
});
|
|
|
|
testWidgets('Image cache resize downscale display 5', (WidgetTester tester) async {
|
|
final Key key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageCacheResized(image, key, 5, 5, 1, 1));
|
|
expect(getRenderImage(tester, key).size, const Size(5.0, 5.0));
|
|
});
|
|
|
|
// For low-resolution screens we prefer higher-resolution images due to
|
|
// visible physical pixel size (see the test for 1.75 DPR above). However,
|
|
// if higher resolution assets are not available we will pick the best
|
|
// available.
|
|
testWidgets('Low-resolution assets', (WidgetTester tester) async {
|
|
final AssetBundle bundle = TestAssetBundle(manifest: '''
|
|
{
|
|
"assets/image.png" : [
|
|
"assets/image.png",
|
|
"assets/1.5x/image.png"
|
|
]
|
|
}
|
|
''');
|
|
|
|
Future<void> testRatio({required double ratio, required double expectedScale}) async {
|
|
Key key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, false, images, bundle));
|
|
expect(getRenderImage(tester, key).size, const Size(200.0, 200.0));
|
|
expect(getRenderImage(tester, key).scale, expectedScale);
|
|
key = GlobalKey();
|
|
await pumpTreeToLayout(tester, buildImageAtRatio(image, key, ratio, true, images, bundle));
|
|
expect(getRenderImage(tester, key).size, const Size(48.0, 48.0));
|
|
expect(getRenderImage(tester, key).scale, expectedScale);
|
|
}
|
|
|
|
// Choose higher resolution image as it's the lowest available.
|
|
await testRatio(ratio: 0.25, expectedScale: 1.0);
|
|
await testRatio(ratio: 0.5, expectedScale: 1.0);
|
|
await testRatio(ratio: 0.75, expectedScale: 1.0);
|
|
await testRatio(ratio: 1.0, expectedScale: 1.0);
|
|
|
|
// Choose higher resolution image even though a lower resolution
|
|
// image is closer.
|
|
await testRatio(ratio: 1.20, expectedScale: 1.5);
|
|
|
|
// Choose higher resolution image because it's closer.
|
|
await testRatio(ratio: 1.25, expectedScale: 1.5);
|
|
await testRatio(ratio: 1.5, expectedScale: 1.5);
|
|
|
|
// Choose lower resolution image because no higher resolution assets
|
|
// are not available.
|
|
await testRatio(ratio: 1.75, expectedScale: 1.5);
|
|
await testRatio(ratio: 2.0, expectedScale: 1.5);
|
|
await testRatio(ratio: 2.25, expectedScale: 1.5);
|
|
await testRatio(ratio: 10.0, expectedScale: 1.5);
|
|
});
|
|
}
|