test: reduce duplicate cursor fixtures and geometry checks

This commit is contained in:
fufesou
2026-09-12 23:32:26 +08:00
parent 5a89aa8c58
commit feff308f11
3 changed files with 49 additions and 100 deletions

View File

@@ -6,7 +6,6 @@ library;
import 'dart:convert';
import 'dart:js' as js;
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/widgets.dart';
@@ -20,13 +19,6 @@ import 'package:get/get.dart';
import 'package:image/image.dart' as img;
import 'package:provider/provider.dart';
class _CursorModel extends Fake implements model.CursorModel {
@override
final Set<String> cachedKeys = {};
@override
void addKey(String key) => cachedKeys.add(key);
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
_alphaTests();
@@ -37,15 +29,10 @@ void main() {
}
}
test('Web cursor aligns CSS hotspots with rounded PNG dimensions', () async {
Map<String, dynamic>? registered;
final original = js.context['setByName'];
js.context['setByName'] = js.allowInterop((String name, String value) {
expect(name, 'cursor');
registered = jsonDecode(value) as Map<String, dynamic>;
});
addTearDown(() => js.context['setByName'] = original);
final nativeImage = await createTestImage(width: 48, height: 48);
addTearDown(nativeImage.dispose);
final registered = _captureCursor();
final canvas = _Canvas(kRemoteViewStyleAdaptive);
addTearDown(canvas.dispose);
final ffi = _FFI(canvas);
for (final (hotspot, scale, side, expected) in [
((7.0, 7.0), 633 / 1600, 19, (3, 3)),
((21.0, 23.0), 633 / 1600, 19, (8, 9)),
@@ -54,37 +41,20 @@ void main() {
((21.0, 23.0), 0.5, 24, (11, 12)),
((7.0, 7.0), 1.0, 48, (7, 7)),
]) {
final cache = _data(nativeImage, hotspot);
final cursor = await _loadCursor(ffi, '$hotspot-$scale',
hotspot: hotspot, pixelRatio: null);
final session =
buildCursorOfCache(_CursorModel(), scale, cache).createSession(1);
buildCursorOfCache(cursor, scale, cursor.cache).createSession(1);
await session.activate();
final uri = Uri.parse(registered!['url'] as String);
final uri = Uri.parse(registered['url'] as String);
final bitmap = img.decodePng(uri.data!.contentAsBytes())!;
expect((bitmap.width, bitmap.height), (side, side));
expect((registered!['hotx'], registered!['hoty']), expected);
expect((registered['hotx'], registered['hoty']), expected);
session.dispose();
await deleteCustomCursor(cache.updateGetKey(scale));
}
});
}
model.CursorData _data(ui.Image nativeImage, (double, double) hotspot) {
final image = img.Image(width: 48, height: 48, numChannels: 4);
img.fill(image, color: img.ColorRgba8(255, 255, 255, 255));
return model.CursorData(
peerId: 'web-cursor-test',
id: '$hotspot',
image: image,
nativeImage: nativeImage,
scale: 1,
data: Uint8List.fromList(img.encodePng(image)),
hotxOrigin: hotspot.$1,
hotyOrigin: hotspot.$2,
width: 48,
height: 48,
);
}
class _Image extends ChangeNotifier implements model.ImageModel {
@override
bool get useTextureRender => false;
@@ -144,18 +114,21 @@ class _FFI extends Fake implements model.FFI {
Future<model.CursorModel> _loadCursor(model.FFI ffi, String id,
{List<int> pixel = const [255, 255, 255, 255],
(double, double) hotspot = (7, 9),
double? pixelRatio = 1}) async {
final cursor = model.CursorModel(WeakReference(ffi))..id = id;
await cursor.updateCursorData({
'id': id,
'width': '48',
'height': '48',
'hotx': '7',
'hoty': '9',
'hotx': '${hotspot.$1}',
'hoty': '${hotspot.$2}',
if (pixelRatio != null) 'scale': '$pixelRatio',
'colors': jsonEncode([for (var i = 0; i < 48 * 48; i++) ...pixel]),
});
addTearDown(() async {
// Keep the session owner alive across asynchronous image decoding.
expect(cursor.parent.target, same(ffi));
for (final key in cursor.cachedKeys) {
await deleteCustomCursor(key);
}
@@ -224,23 +197,22 @@ Future<void> _checkPolicy(WidgetTester tester, String style, double dpr) async {
}
void _alphaTests() {
for (final (density, pixel, expected) in [
(1.0, [255, 255, 255, 128], [255, 255, 255, 128]),
(2.0, [255, 128, 64, 128], [255, 128, 64, 128]),
(1.0, [255, 128, 64, 112], [255, 128, 64, 112]),
(1.0, [0, 0, 0, 0], [0, 0, 0, 0]),
(1.0, [255, 255, 255, 255], [255, 255, 255, 255]),
for (final (density, pixel) in [
(1.0, [255, 255, 255, 128]),
(2.0, [255, 128, 64, 128]),
(1.0, [255, 128, 64, 112]),
(1.0, [0, 0, 0, 0]),
(1.0, [255, 255, 255, 255]),
// Old macOS hosts send straight alpha without density metadata.
(0.0, [255, 128, 64, 128], [255, 128, 64, 128]),
(null, [80, 40, 20, 128], [80, 40, 20, 128]),
(0.0, [255, 128, 64, 128]),
(null, [80, 40, 20, 128]),
]) {
test('Web macOS cursor density $density preserves RGBA $expected',
() => _checkAlpha(density, pixel, expected));
test('Web macOS cursor density $density preserves RGBA $pixel',
() => _checkAlpha(density, pixel));
}
}
Future<void> _checkAlpha(
double? density, List<int> pixel, List<int> expected) async {
Future<void> _checkAlpha(double? density, List<int> pixel) async {
final registered = _captureCursor();
final canvas = _Canvas(kRemoteViewStyleAdaptive);
addTearDown(canvas.dispose);
@@ -250,7 +222,7 @@ Future<void> _checkAlpha(
// Cover both the painted remote cursor and the initial CSS cursor PNG.
final straight = await cursor.image!
.toByteData(format: ui.ImageByteFormat.rawStraightRgba);
expect(straight!.buffer.asUint8List(0, 4), expected);
expect(straight!.buffer.asUint8List(0, 4), pixel);
for (final scale in [1.0, 0.5, 1.0]) {
final session =
buildCursorOfCache(cursor, scale, cursor.cache).createSession(1);
@@ -259,6 +231,6 @@ Future<void> _checkAlpha(
final png =
img.decodePng(Uri.parse(registered['url']).data!.contentAsBytes())!;
final color = png.getPixel(png.width ~/ 2, png.height ~/ 2);
expect([color.r, color.g, color.b, color.a], expected);
expect([color.r, color.g, color.b, color.a], pixel);
}
}