Files
MrbWebLibre/apps/weblibre/test/presentation/widgets/url_icon_test.dart
T
2026-05-22 19:16:03 +02:00

169 lines
4.8 KiB
Dart

import 'dart:convert';
import 'dart:typed_data';
import 'package:drift/native.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mozilla_components/flutter_mozilla_components.dart';
import 'package:flutter_mozilla_components/src/pigeons/gecko.g.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:weblibre/data/database/functions/lexo_rank_functions.dart';
import 'package:weblibre/domain/services/favicon_resolver.dart';
import 'package:weblibre/domain/services/generic_website.dart';
import 'package:weblibre/features/user/data/database/database.dart';
import 'package:weblibre/features/user/data/providers.dart';
import 'package:weblibre/features/user/domain/providers.dart';
import 'package:weblibre/presentation/widgets/url_icon.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test('selectFirstCachedIconBytes preserves url-list fallback order', () {
final secondBytes = Uint8List.fromList([2, 3, 4]);
final selected = selectFirstCachedIconBytes([
null,
secondBytes,
Uint8List.fromList([9, 9, 9]),
]);
expect(selected, same(secondBytes));
});
testWidgets('distinct cached icons do not collapse to one image', (
tester,
) async {
final firstUrl = Uri.parse('https://first.example/result');
final secondUrl = Uri.parse('https://second.example/result');
final db = UserDatabase(
NativeDatabase.memory(
setup: (database) {
registerLexorankFunctions(database);
},
),
);
addTearDown(() async {
await db.close();
});
await tester.pumpWidget(
ProviderScope(
overrides: [
userDatabaseProvider.overrideWith((ref) => db),
faviconResolverProvider.overrideWithValue(_NeverCalledResolver()),
geckoIconServiceProvider.overrideWithValue(_FakeGeckoIconService()),
watchCachedIconBytesProvider(
firstUrl.origin,
).overrideWith((ref) => Stream.value(_firstSvgBytes)),
watchCachedIconBytesProvider(
secondUrl.origin,
).overrideWith((ref) => Stream.value(_secondSvgBytes)),
],
child: MaterialApp(
home: Row(
children: [
UrlIcon([firstUrl], iconSize: 24, cacheOnly: true),
UrlIcon([secondUrl], iconSize: 24, cacheOnly: true),
],
),
),
),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
final rawImages = tester
.widgetList<RawImage>(find.byType(RawImage))
.toList();
expect(rawImages, hasLength(2));
expect(identical(rawImages[0].image, rawImages[1].image), isFalse);
await tester.pumpWidget(const SizedBox.shrink());
await tester.pumpAndSettle();
});
testWidgets('cacheOnly falls back to a generated icon on cache miss', (
tester,
) async {
final db = UserDatabase(
NativeDatabase.memory(
setup: (database) {
registerLexorankFunctions(database);
},
),
);
addTearDown(() async {
await db.close();
});
await tester.pumpWidget(
ProviderScope(
overrides: [
userDatabaseProvider.overrideWith((ref) => db),
faviconResolverProvider.overrideWithValue(_NeverCalledResolver()),
geckoIconServiceProvider.overrideWithValue(_FakeGeckoIconService()),
],
child: MaterialApp(
home: UrlIcon(
[Uri.parse('https://generated.example')],
iconSize: 24,
cacheOnly: true,
),
),
),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(find.byType(RawImage), findsOneWidget);
await tester.pumpWidget(const SizedBox.shrink());
await tester.pumpAndSettle();
});
}
final class _NeverCalledResolver implements FaviconResolver {
@override
Future<FaviconResolveResult> resolve(Uri url, {int? proxyPort}) {
throw UnimplementedError('cacheOnly should not hit the resolver');
}
}
final class _FakeGeckoIconService extends GeckoIconService {
@override
Future<IconResult> loadIcon({
required Uri url,
List<Resource> resources = const [],
IconSize size = IconSize.defaultSize,
bool isPrivate = false,
bool waitOnNetworkLoad = true,
}) async {
return IconResult(
image: _firstSvgBytes,
color: null,
source: IconSource.generator,
maskable: false,
);
}
}
final _firstSvgBytes = Uint8List.fromList(
utf8.encode('''
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<rect width="16" height="16" fill="#ff0000"/>
</svg>
'''),
);
final _secondSvgBytes = Uint8List.fromList(
utf8.encode('''
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="8" fill="#0000ff"/>
</svg>
'''),
);