Files
buzz/mobile/test/shared/widgets/frosted_scaffold_test.dart
klopez4212 93b8b8febc Standardize mobile navigation edges (#8895)
Standardizes mobile navigation headers: Android uses Home’s shared
Flutter blur; iOS uses native soft edges, with graduated material
retained during large-title collapse and the iOS 26 fallback preserved.

Preserves Home’s upward title motion with a soft boundary and keeps the
avatar crop title visible.

Validation: 4,112 Flutter tests and 111 native tests passed after
merging main. Rendered partial-scroll regression verified on the iOS 27
simulator; removing either review fix fails its regression assertion.
The earlier preview was installed on a physical iPhone with the existing
profile.

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz>
2026-11-01 21:41:40 +00:00

288 lines
8.4 KiB
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/material.dart';
import 'package:flutter_test/flutter_test.dart';
Border? _appBarBorder(WidgetTester tester) {
final container = tester.widget<Container>(
find.byKey(const ValueKey('frosted-app-bar-background')),
);
return (container.decoration as BoxDecoration?)?.border as Border?;
}
void main() {
testWidgets('unbounded reversed scroll depth stays finite for native bar', (
tester,
) async {
await tester.pumpWidget(
const MaterialApp(
home: FrostedScaffold(
appBar: FrostedAppBar(title: Text('Conversation')),
body: SizedBox(key: ValueKey('timeline')),
),
),
);
final source = tester.element(find.byKey(const ValueKey('timeline')));
final offset = IosNavigationScrollScope.maybeOf(source)!;
ScrollUpdateNotification(
metrics: FixedScrollMetrics(
minScrollExtent: 0,
maxScrollExtent: double.infinity,
pixels: 0,
viewportDimension: 600,
axisDirection: AxisDirection.up,
devicePixelRatio: 1,
),
context: source,
scrollDelta: 0,
).dispatch(source);
await tester.pump();
expect(offset.value, 600);
});
testWidgets('foreground paints above header and leaves body interactive', (
tester,
) async {
var taps = 0;
const marker = Text('Foreground date');
const bar = FrostedAppBar(title: Text('Header'));
await tester.pumpWidget(
MaterialApp(
home: FrostedScaffold(
appBar: bar,
body: Center(
child: TextButton(
onPressed: () => taps++,
child: const Text('Body action'),
),
),
foreground: const Stack(
children: [Positioned(top: 80, left: 0, child: marker)],
),
),
),
);
final stack = tester.widget<Stack>(
find.ancestor(of: find.byWidget(bar), matching: find.byType(Stack)).first,
);
expect(stack.children.indexOf(bar), lessThan(stack.children.length - 1));
expect(
find.descendant(
of: find.byWidget(stack.children.last),
matching: find.byWidget(marker),
),
findsOneWidget,
);
await tester.tap(find.text('Body action'));
expect(taps, 1);
});
testWidgets('utility pages expose the inverted surface hierarchy', (
tester,
) async {
const pageSurface = Color(0xFFEEEEEE);
const containerSurface = Color(0xFFFFFFFF);
await tester.pumpWidget(
MaterialApp(
theme: AppTheme.light(
colorScheme: lightColorScheme.copyWith(
surface: containerSurface,
surfaceContainerHighest: pageSurface,
),
),
home: FrostedScaffold(
useUtilitySurfaceTheme: true,
appBar: const FrostedAppBar(title: Text('Settings')),
body: Builder(
builder: (context) => Column(
children: [
ColoredBox(
key: const ValueKey('utility-page-surface'),
color: Theme.of(context).colorScheme.surface,
child: const SizedBox.square(dimension: 20),
),
ColoredBox(
key: const ValueKey('utility-container-surface'),
color: Theme.of(context).colorScheme.surfaceContainerHighest,
child: const SizedBox.square(dimension: 20),
),
],
),
),
),
),
);
expect(
tester
.widget<ColoredBox>(
find.byKey(const ValueKey('utility-page-surface')),
)
.color,
pageSurface,
);
expect(
tester
.widget<ColoredBox>(
find.byKey(const ValueKey('utility-container-surface')),
)
.color,
containerSurface,
);
});
for (final reverse in [false, true]) {
testWidgets(
'composer scrolling preserves timeline frost reverse=$reverse',
(tester) async {
tester.view.physicalSize = const Size(400, 800);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final timeline = ScrollController(
initialScrollOffset: reverse ? 0 : 120,
);
addTearDown(timeline.dispose);
final composer = TextEditingController();
addTearDown(composer.dispose);
await tester.pumpWidget(
MaterialApp(
theme: AppTheme.light(),
home: FrostedScaffold(
appBar: const FrostedAppBar(title: Text('Conversation')),
body: Column(
children: [
Expanded(
child: ListView.builder(
reverse: reverse,
controller: timeline,
itemCount: 40,
itemBuilder: (_, index) =>
SizedBox(height: 48, child: Text('Message $index')),
),
),
SizedBox(
height: 100,
child: TextField(
controller: composer,
minLines: 1,
maxLines: 3,
),
),
],
),
),
),
);
await tester.pumpAndSettle();
expect(
find.byWidgetPredicate(
(widget) => widget is BackdropFilter && widget.enabled,
),
findsOneWidget,
);
await tester.tap(find.byType(TextField));
tester.view.viewInsets = const FakeViewPadding(bottom: 300);
await tester.enterText(
find.byType(TextField),
'One\nTwo\nThree\nFour\nFive',
);
await tester.pumpAndSettle();
expect(
find.byWidgetPredicate(
(widget) => widget is BackdropFilter && widget.enabled,
),
findsOneWidget,
);
await tester.enterText(find.byType(TextField), '');
await tester.pumpAndSettle();
expect(
find.byWidgetPredicate(
(widget) => widget is BackdropFilter && widget.enabled,
),
findsOneWidget,
);
expect(_appBarBorder(tester), isNull);
timeline.jumpTo(reverse ? timeline.position.maxScrollExtent : 0);
await tester.pumpAndSettle();
expect(
find.byWidgetPredicate(
(widget) => widget is BackdropFilter && widget.enabled,
),
findsNothing,
);
await tester.enterText(
find.byType(TextField),
'One\nTwo\nThree\nFour\nFive',
);
await tester.pumpAndSettle();
expect(
find.byWidgetPredicate(
(widget) => widget is BackdropFilter && widget.enabled,
),
findsNothing,
);
},
);
}
testWidgets('page frost follows scrolling without a dividing line', (
tester,
) async {
await tester.pumpWidget(
MaterialApp(
theme: AppTheme.light(),
home: FrostedScaffold(
appBar: const FrostedAppBar(title: Text('Theme')),
body: ListView.builder(
key: const ValueKey('page-scroll-view'),
padding: const EdgeInsets.only(top: 57),
itemCount: 40,
itemBuilder: (_, index) =>
SizedBox(height: 48, child: Text('Theme option $index')),
),
),
),
);
expect(_appBarBorder(tester), isNull);
expect(
find.byWidgetPredicate(
(widget) => widget is BackdropFilter && widget.enabled,
),
findsNothing,
);
await tester.drag(
find.byKey(const ValueKey('page-scroll-view')),
const Offset(0, -120),
);
await tester.pumpAndSettle();
final border = _appBarBorder(tester);
expect(border, isNull);
expect(
find.byWidgetPredicate(
(widget) => widget is BackdropFilter && widget.enabled,
),
findsOneWidget,
);
await tester.drag(
find.byKey(const ValueKey('page-scroll-view')),
const Offset(0, 500),
);
await tester.pumpAndSettle();
expect(_appBarBorder(tester), isNull);
expect(
find.byWidgetPredicate(
(widget) => widget is BackdropFilter && widget.enabled,
),
findsNothing,
);
});
}