mirror of
https://github.com/block/buzz.git
synced 2026-10-29 13:31:44 +00:00
## Summary Replace the community switch sheet with a two-column picker. Selection enlarges the avatar, waits with the voice-note loader for destination channel content and layout to settle, then follows a curved path into the header. Cache raster icons at a maximum 512-pixel size and accept only resource-free emoji SVGs. Cancel timed-out or disposed downloads without delaying content readiness. Animate Edit badges in place, and open Add Community on the pairing welcome page. Community removal lives only in the switcher’s Edit mode. Keep the flow shared across Android/iOS, with native iOS controls and removal confirmation, and clear navigation backgrounds at rest. ### Related issue Related to #8070 (community icon refresh/caching). ### Testing - 122 focused Flutter regression tests and seven native iOS tests passed after merging the latest navigation changes from `main`. Earlier feature build installed and launched on a physical iPhone. - Full mobile pre-push checks passed; regression coverage includes gradient rendering, rejected/oversized icons, bounded persisted raster decoding, SVG resource rejection and cache migration, timeout/disposal cancellation, disconnected destination channels, degraded unread-history settlement, and switching while icons refresh. Full `just ci` stopped during Rust checks for low disk space. Hosted CI remains required. - [Grid and Edit previews](https://github.com/block/buzz/pull/8076#issuecomment-5977838957). --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
676 lines
22 KiB
Dart
676 lines
22 KiB
Dart
import 'dart:async';
|
|
import 'package:buzz/shared/relay/media_image.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:http/testing.dart';
|
|
import 'package:buzz/shared/theme/theme.dart';
|
|
import 'package:buzz/shared/widgets/frosted_app_bar.dart';
|
|
import 'package:buzz/shared/widgets/frosted_scaffold.dart';
|
|
import 'package:buzz/shared/widgets/ios_navigation_bar.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
|
|
void main() {
|
|
testWidgets(
|
|
'native readiness waits for configuration and layout acknowledgement',
|
|
(tester) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
const channel = MethodChannel('buzz/ios_navigation_bar/844');
|
|
final applied = Completer<void>();
|
|
final readiness = <bool>[];
|
|
final calls = <String>[];
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(channel, (
|
|
call,
|
|
) async {
|
|
calls.add(call.method);
|
|
if (call.method == 'prepareForReveal') await applied.future;
|
|
return null;
|
|
});
|
|
addTearDown(
|
|
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
channel,
|
|
null,
|
|
),
|
|
);
|
|
await tester.pumpWidget(
|
|
_testApp(
|
|
home: SizedBox(
|
|
height: 100,
|
|
child: IosNavigationBar(
|
|
title: 'Bravo',
|
|
onReadyChanged: readiness.add,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
tester.widget<UiKitView>(find.byType(UiKitView)).onPlatformViewCreated!(
|
|
844,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(calls, contains('prepareForReveal'));
|
|
expect(readiness.last, false);
|
|
applied.complete();
|
|
await tester.pumpAndSettle();
|
|
await tester.pump();
|
|
await tester.pump();
|
|
expect(readiness.last, true);
|
|
await tester.pumpWidget(const SizedBox());
|
|
debugDefaultTargetPlatformOverride = null;
|
|
},
|
|
);
|
|
|
|
testWidgets('native landing ignores pending profile artwork', (tester) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
final pending = Completer<http.Response>();
|
|
var requested = false;
|
|
final client = MockClient((_) {
|
|
requested = true;
|
|
return pending.future;
|
|
});
|
|
addTearDown(client.close);
|
|
const channel = MethodChannel('buzz/ios_navigation_bar/846');
|
|
final readiness = <bool>[];
|
|
final calls = <String>[];
|
|
final createdChannels = <MethodChannel>[];
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.platform_views,
|
|
(call) async {
|
|
if (call.method == 'create') {
|
|
final id = (call.arguments as Map)['id'];
|
|
final native = MethodChannel('${IosNavigationBar.viewType}/$id');
|
|
createdChannels.add(native);
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
native,
|
|
(call) async {
|
|
calls.add(call.method);
|
|
return null;
|
|
},
|
|
);
|
|
}
|
|
return null;
|
|
},
|
|
);
|
|
addTearDown(() {
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.platform_views,
|
|
null,
|
|
);
|
|
for (final native in createdChannels) {
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
native,
|
|
null,
|
|
);
|
|
}
|
|
});
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(channel, (
|
|
call,
|
|
) async {
|
|
calls.add(call.method);
|
|
return null;
|
|
});
|
|
addTearDown(
|
|
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
channel,
|
|
null,
|
|
),
|
|
);
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [mediaHttpClientProvider.overrideWithValue(client)],
|
|
child: _testApp(
|
|
home: IosNavigationBar(
|
|
title: 'Bravo',
|
|
leading: IosNavigationAction(
|
|
label: 'Community settings',
|
|
avatarInitial: 'B',
|
|
onAvatarBoundsChanged: (_) {},
|
|
),
|
|
actions: const [
|
|
IosNavigationAction(
|
|
label: 'Profile',
|
|
avatarInitial: 'P',
|
|
imageUrl: 'https://slow.example.com/profile.png',
|
|
),
|
|
],
|
|
onReadyChanged: readiness.add,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
tester.widget<UiKitView>(find.byType(UiKitView)).onPlatformViewCreated!(
|
|
846,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
Map leading() =>
|
|
(tester.widget<UiKitView>(find.byType(UiKitView)).creationParams
|
|
as Map)['leading']
|
|
as Map;
|
|
await tester.runAsync(() async {
|
|
for (
|
|
var attempt = 0;
|
|
attempt < 100 && leading()['imageData'] == null;
|
|
attempt++
|
|
) {
|
|
await Future<void>.delayed(const Duration(milliseconds: 10));
|
|
await tester.pump();
|
|
}
|
|
});
|
|
await tester.pumpAndSettle();
|
|
expect(requested, isTrue);
|
|
expect(pending.isCompleted, isFalse);
|
|
expect(calls, contains('prepareForReveal'));
|
|
expect(readiness.last, isTrue);
|
|
expect(leading()['imageData'], isNotNull);
|
|
pending.complete(http.Response('offline', 503));
|
|
await tester.pumpAndSettle();
|
|
await tester.pumpWidget(const SizedBox());
|
|
debugDefaultTargetPlatformOverride = null;
|
|
});
|
|
|
|
testWidgets(
|
|
'native avatar reports global transition bounds and retains a hidden slot',
|
|
(tester) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
const channel = MethodChannel('${IosNavigationBar.viewType}/842');
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
channel,
|
|
(_) async => null,
|
|
);
|
|
addTearDown(
|
|
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
channel,
|
|
null,
|
|
),
|
|
);
|
|
Rect? measured;
|
|
await tester.pumpWidget(
|
|
_testApp(
|
|
home: Padding(
|
|
padding: const EdgeInsets.only(top: 30),
|
|
child: IosNavigationBar(
|
|
title: 'Alpha',
|
|
leading: IosNavigationAction(
|
|
label: 'Community settings',
|
|
avatarInitial: 'A',
|
|
avatarHidden: true,
|
|
onAvatarBoundsChanged: (bounds) => measured = bounds,
|
|
onPressed: () {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final view = tester.widget<UiKitView>(find.byType(UiKitView));
|
|
final leading = (view.creationParams as Map)['leading'] as Map;
|
|
expect(leading['tracksAvatarBounds'], isTrue);
|
|
expect(leading['avatarHidden'], isTrue);
|
|
view.onPlatformViewCreated!(842);
|
|
await tester.pump();
|
|
await tester.binding.defaultBinaryMessenger.handlePlatformMessage(
|
|
channel.name,
|
|
channel.codec.encodeMethodCall(
|
|
const MethodCall('avatarBounds', {
|
|
'id': 'leading',
|
|
'x': 16.0,
|
|
'y': 4.0,
|
|
'width': 36.0,
|
|
'height': 36.0,
|
|
}),
|
|
),
|
|
(_) {},
|
|
);
|
|
expect(measured, const Rect.fromLTWH(16, 34, 36, 36));
|
|
await tester.pumpWidget(const SizedBox());
|
|
debugDefaultTargetPlatformOverride = null;
|
|
},
|
|
);
|
|
|
|
for (final reverse in [false, true]) {
|
|
testWidgets('initial material tracks content above reverse=$reverse', (
|
|
tester,
|
|
) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
final controller = ScrollController();
|
|
addTearDown(controller.dispose);
|
|
ValueListenable<double>? offset;
|
|
Widget page(int count) => _testApp(
|
|
home: FrostedScaffold(
|
|
appBar: const FrostedAppBar(title: Text('Conversation')),
|
|
body: Builder(
|
|
builder: (context) {
|
|
offset = IosNavigationScrollScope.maybeOf(context);
|
|
return ListView(
|
|
controller: controller,
|
|
reverse: reverse,
|
|
children: [
|
|
for (var i = 0; i < count; i++)
|
|
SizedBox(height: 60, child: Text('Message $i')),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpWidget(page(40));
|
|
await tester.pumpAndSettle();
|
|
expect(offset!.value, reverse ? greaterThan(0) : 0);
|
|
controller.jumpTo(controller.position.maxScrollExtent);
|
|
await tester.pumpAndSettle();
|
|
expect(offset!.value, reverse ? 0 : greaterThan(0));
|
|
await tester.pumpWidget(page(1));
|
|
await tester.pumpAndSettle();
|
|
expect(offset!.value, 0);
|
|
await tester.pumpWidget(const SizedBox());
|
|
debugDefaultTargetPlatformOverride = null;
|
|
});
|
|
}
|
|
|
|
testWidgets('native material follows live theme surface changes', (
|
|
tester,
|
|
) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
const channel = MethodChannel('buzz/ios_navigation_bar/845');
|
|
final configurations = <Map<Object?, Object?>>[];
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(channel, (
|
|
call,
|
|
) async {
|
|
if (call.method == 'configure') configurations.add(call.arguments as Map);
|
|
return null;
|
|
});
|
|
addTearDown(
|
|
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
channel,
|
|
null,
|
|
),
|
|
);
|
|
Widget app(Color surface) => ProviderScope(
|
|
child: MaterialApp(
|
|
theme: ThemeData(
|
|
colorScheme: ColorScheme.fromSeed(
|
|
seedColor: surface,
|
|
).copyWith(surface: surface),
|
|
),
|
|
home: const Scaffold(
|
|
body: IosNavigationBar(title: 'general', subtitle: '3 members'),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpWidget(app(const Color(0xFFFFEEDD)));
|
|
final view = tester.widget<UiKitView>(find.byType(UiKitView));
|
|
expect(view.creationParams, containsPair('background', 0xFFFFEEDD));
|
|
view.onPlatformViewCreated!(845);
|
|
await tester.pump();
|
|
await tester.pumpWidget(app(const Color(0xFF223344)));
|
|
await tester.pumpAndSettle();
|
|
expect(configurations.last['background'], 0xFF223344);
|
|
await tester.pumpWidget(const SizedBox());
|
|
debugDefaultTargetPlatformOverride = null;
|
|
});
|
|
|
|
testWidgets('larger native metrics keep a deeply scrolled title collapsed', (
|
|
tester,
|
|
) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
const channel = MethodChannel('buzz/ios_navigation_bar/843');
|
|
final calls = <MethodCall>[];
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(channel, (
|
|
call,
|
|
) async {
|
|
calls.add(call);
|
|
return null;
|
|
});
|
|
addTearDown(
|
|
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
channel,
|
|
null,
|
|
),
|
|
);
|
|
await tester.pumpWidget(
|
|
_testApp(
|
|
home: FrostedScaffold(
|
|
nativePinnedBody: true,
|
|
appBar: const FrostedAppBar(
|
|
title: Text('Search'),
|
|
nativeLargeTitle: true,
|
|
),
|
|
body: ListView(
|
|
children: [
|
|
for (var i = 0; i < 40; i++)
|
|
SizedBox(height: 60, child: Text('Row $i')),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
tester.widget<UiKitView>(find.byType(UiKitView)).onPlatformViewCreated!(
|
|
843,
|
|
);
|
|
await tester.pump();
|
|
await tester.drag(find.byType(ListView), const Offset(0, -250));
|
|
await tester.pumpAndSettle();
|
|
expect(calls.where((call) => call.method == 'scroll').last.arguments, 52);
|
|
final bodyTop = tester.getTopLeft(find.byType(ListView)).dy;
|
|
await tester.binding.defaultBinaryMessenger.handlePlatformMessage(
|
|
channel.name,
|
|
channel.codec.encodeMethodCall(
|
|
const MethodCall('metrics', {
|
|
'compactHeight': 44.0,
|
|
'expandedHeight': 124.0,
|
|
}),
|
|
),
|
|
(_) {},
|
|
);
|
|
await tester.pump();
|
|
await tester.pump();
|
|
expect(calls.where((call) => call.method == 'scroll').last.arguments, 80);
|
|
expect(tester.getSize(find.byType(UiKitView)).height, 44);
|
|
expect(tester.getTopLeft(find.byType(ListView)).dy, bodyTop);
|
|
await tester.pumpWidget(const SizedBox());
|
|
debugDefaultTargetPlatformOverride = null;
|
|
});
|
|
|
|
testWidgets(
|
|
'iOS routes use UIKit and route native menus to current callbacks',
|
|
(tester) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
const channel = MethodChannel('${IosNavigationBar.viewType}/42');
|
|
final calls = <MethodCall>[];
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(channel, (
|
|
call,
|
|
) async {
|
|
calls.add(call);
|
|
return null;
|
|
});
|
|
addTearDown(
|
|
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
channel,
|
|
null,
|
|
),
|
|
);
|
|
var selected = 0;
|
|
var callbackVersion = 1;
|
|
Widget page() {
|
|
final version = callbackVersion;
|
|
return _testApp(
|
|
theme: AppTheme.light(),
|
|
home: FrostedScaffold(
|
|
appBar: FrostedAppBar(
|
|
nativeTitle: 'Activity',
|
|
nativeLargeTitle: true,
|
|
nativeActions: [
|
|
IosNavigationAction(
|
|
label: 'Filter',
|
|
symbol: 'line.3.horizontal.decrease',
|
|
children: [
|
|
IosNavigationAction(
|
|
label: 'Mentions',
|
|
onPressed: () => selected = version,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
body: ListView(
|
|
children: [
|
|
for (var i = 0; i < 40; i++)
|
|
SizedBox(height: 60, child: Text('Item $i')),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(page());
|
|
final view = tester.widget<UiKitView>(find.byType(UiKitView));
|
|
expect(view.viewType, IosNavigationBar.viewType);
|
|
expect(view.creationParams, containsPair('largeTitle', true));
|
|
view.onPlatformViewCreated!(42);
|
|
await tester.pump();
|
|
callbackVersion = 2;
|
|
await tester.pumpWidget(page());
|
|
await tester.binding.defaultBinaryMessenger.handlePlatformMessage(
|
|
channel.name,
|
|
channel.codec.encodeMethodCall(const MethodCall('action', '0.0')),
|
|
(_) {},
|
|
);
|
|
expect(selected, 2);
|
|
await tester.drag(find.byType(ListView), const Offset(0, -180));
|
|
await tester.pumpAndSettle();
|
|
expect(calls.where((call) => call.method == 'scroll').last.arguments, 52);
|
|
expect(tester.takeException(), isNull);
|
|
await tester.pumpWidget(const SizedBox());
|
|
debugDefaultTargetPlatformOverride = null;
|
|
},
|
|
);
|
|
|
|
testWidgets('native back button respects the Flutter route stack', (
|
|
tester,
|
|
) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
final navigator = GlobalKey<NavigatorState>();
|
|
await tester.pumpWidget(
|
|
_testApp(
|
|
navigatorKey: navigator,
|
|
home: const Scaffold(body: Text('Root')),
|
|
),
|
|
);
|
|
navigator.currentState!.push(
|
|
MaterialPageRoute<void>(
|
|
builder: (_) => const FrostedScaffold(
|
|
appBar: FrostedAppBar(title: Text('Theme')),
|
|
body: SizedBox.expand(),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
final native = tester.widget<IosNavigationBar>(
|
|
find.byType(IosNavigationBar),
|
|
);
|
|
expect(native.title, 'Theme');
|
|
expect(native.onBack, isNotNull);
|
|
native.onBack!();
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Root'), findsOneWidget);
|
|
expect(find.byType(UiKitView), findsNothing);
|
|
debugDefaultTargetPlatformOverride = null;
|
|
});
|
|
|
|
testWidgets('UIKit measurements resize the bar and page spacing together', (
|
|
tester,
|
|
) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
const channel = MethodChannel('${IosNavigationBar.viewType}/91');
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
channel,
|
|
(_) async => null,
|
|
);
|
|
addTearDown(
|
|
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
channel,
|
|
null,
|
|
),
|
|
);
|
|
await tester.pumpWidget(
|
|
_testApp(
|
|
home: Builder(
|
|
builder: (context) => FrostedScaffold(
|
|
appBar: const FrostedAppBar(
|
|
title: Text('Home'),
|
|
nativeLargeTitle: true,
|
|
),
|
|
body: ListView(
|
|
children: [
|
|
SizedBox(
|
|
key: const ValueKey('body-inset'),
|
|
height: frostedAppBarHeight(context, nativeLargeTitle: true),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final view = tester.widget<UiKitView>(find.byType(UiKitView));
|
|
view.onPlatformViewCreated!(91);
|
|
await tester.pump();
|
|
await tester.binding.defaultBinaryMessenger.handlePlatformMessage(
|
|
channel.name,
|
|
channel.codec.encodeMethodCall(
|
|
const MethodCall('metrics', {
|
|
'compactHeight': 56.0,
|
|
'expandedHeight': 112.0,
|
|
}),
|
|
),
|
|
(_) {},
|
|
);
|
|
await tester.pump();
|
|
expect(tester.getSize(find.byType(UiKitView)).height, 112);
|
|
expect(
|
|
tester.getSize(find.byKey(const ValueKey('body-inset'))).height,
|
|
112,
|
|
);
|
|
await tester.pumpWidget(const SizedBox());
|
|
debugDefaultTargetPlatformOverride = null;
|
|
});
|
|
|
|
testWidgets('loading avatars use initials instead of navigation symbols', (
|
|
tester,
|
|
) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
await tester.pumpWidget(
|
|
_testApp(
|
|
home: const IosNavigationBar(
|
|
title: 'Home',
|
|
leading: IosNavigationAction(
|
|
label: 'Community',
|
|
symbol: 'building.2',
|
|
avatarInitial: 'K',
|
|
avatarIdentity: 'community-one',
|
|
),
|
|
actions: [
|
|
IosNavigationAction(
|
|
label: 'Profile',
|
|
symbol: 'person.crop.circle',
|
|
avatarInitial: 'A',
|
|
avatarIdentity: 'profile-one',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
final params =
|
|
tester.widget<UiKitView>(find.byType(UiKitView)).creationParams! as Map;
|
|
expect((params['leading'] as Map)['symbol'], isNull);
|
|
expect((params['leading'] as Map)['avatarInitial'], 'K');
|
|
final profile = (params['actions'] as List).single as Map;
|
|
expect(profile['symbol'], isNull);
|
|
expect(profile['avatarInitial'], 'A');
|
|
await tester.pumpWidget(const SizedBox());
|
|
debugDefaultTargetPlatformOverride = null;
|
|
});
|
|
|
|
testWidgets(
|
|
'retains decoded avatars on theme refresh but isolates identities',
|
|
(tester) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
final messenger = tester.binding.defaultBinaryMessenger;
|
|
final channels = <MethodChannel>[];
|
|
messenger.setMockMethodCallHandler(SystemChannels.platform_views, (
|
|
call,
|
|
) async {
|
|
if (call.method == 'create') {
|
|
final id = (call.arguments as Map)['id'];
|
|
final channel = MethodChannel('${IosNavigationBar.viewType}/$id');
|
|
channels.add(channel);
|
|
messenger.setMockMethodCallHandler(channel, (_) async => null);
|
|
}
|
|
return null;
|
|
});
|
|
addTearDown(() {
|
|
messenger.setMockMethodCallHandler(SystemChannels.platform_views, null);
|
|
for (final channel in channels) {
|
|
messenger.setMockMethodCallHandler(channel, null);
|
|
}
|
|
});
|
|
Widget page(String identity, ThemeData theme) => _testApp(
|
|
theme: theme,
|
|
home: IosNavigationBar(
|
|
title: 'Home',
|
|
leading: IosNavigationAction(
|
|
label: 'Profile',
|
|
avatarInitial: 'K',
|
|
avatarIdentity: identity,
|
|
),
|
|
),
|
|
);
|
|
Map avatar() =>
|
|
(tester.widget<UiKitView>(find.byType(UiKitView)).creationParams!
|
|
as Map)['leading']
|
|
as Map;
|
|
await tester.pumpWidget(page('one', AppTheme.light()));
|
|
await tester.runAsync(() async {
|
|
for (
|
|
var attempt = 0;
|
|
attempt < 100 && avatar()['imageData'] == null;
|
|
attempt++
|
|
) {
|
|
await Future<void>.delayed(const Duration(milliseconds: 10));
|
|
await tester.pump();
|
|
}
|
|
});
|
|
final image = avatar()['imageData'];
|
|
expect(image, isNotNull);
|
|
await tester.pumpWidget(page('one', AppTheme.dark()));
|
|
expect(avatar()['imageData'], image);
|
|
await tester.pumpWidget(page('two', AppTheme.dark()));
|
|
expect(avatar()['imageData'], isNull);
|
|
expect(avatar()['symbol'], isNull);
|
|
await tester.pumpWidget(const SizedBox());
|
|
debugDefaultTargetPlatformOverride = null;
|
|
},
|
|
);
|
|
|
|
testWidgets('Android retains its Flutter header', (tester) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
await tester.pumpWidget(
|
|
_testApp(
|
|
theme: AppTheme.light(),
|
|
home: const FrostedScaffold(
|
|
appBar: FrostedAppBar(title: Text('Settings')),
|
|
body: SizedBox.expand(),
|
|
),
|
|
),
|
|
);
|
|
expect(find.byType(UiKitView), findsNothing);
|
|
expect(find.text('Settings'), findsOneWidget);
|
|
debugDefaultTargetPlatformOverride = null;
|
|
});
|
|
}
|
|
|
|
Widget _testApp({
|
|
ThemeData? theme,
|
|
required Widget home,
|
|
GlobalKey<NavigatorState>? navigatorKey,
|
|
}) => ProviderScope(
|
|
child: MaterialApp(
|
|
theme: theme,
|
|
home: home,
|
|
navigatorKey: navigatorKey,
|
|
builder: (context, child) => IosNavigationMetricsHost(child: child!),
|
|
),
|
|
);
|