Files
buzz/mobile/test/shared/widgets/buzz_setting_menu_test.dart
klopez4212 a12ad1cdd2 feat(mobile): refine agent profiles and model selection (#8773)
## Summary
- Simplify agent profiles with Message/Bestie actions, relay-authored
published instructions (unavailable without a config channel),
management details, public-key copying, and a separate final Delete
section.
- Sort the Agents directory by name and mark the selected Bestie with a
muted filled star.
- Place Model and Thinking together below Name. Use native iOS Liquid
Glass menus and preserve selection callbacks as controls become enabled.

Related: #8685.

## Validation
Full mobile checks passed. Regression coverage includes relay-author
filtering, unavailable instructions, metadata retry, resizing native
pickers, accurate deletion state, selection after loading, and Bestie
switching. Real iOS simulator taps verified both pickers; the user
confirmed the earlier iPhone build works. Review fixes were checked with
widget journeys and the native UIKit resize/selection probe.

<details>
<summary>Previews</summary>

Synthetic fixture renders:

| Profile | Create agent | Bestie card |
| --- | --- | --- |
| ![Profile with separate Delete
section](https://github.com/user-attachments/assets/4b6e7cf5-b152-43a3-99d9-7c1ee65d0c70)
| ![Model and Thinking
settings](https://github.com/user-attachments/assets/c81dc138-d5d3-43bb-873b-a64171155a7d)
| ![Muted Bestie
star](https://github.com/user-attachments/assets/5ce95e3b-2403-41c5-b6d6-52b8b60cc7d6)
|

Native iOS simulator interaction with a synthetic catalog:

![Both picker values updated after native menu
selections](https://github.com/user-attachments/assets/a3ee6d4d-91e0-4df6-b4cf-b66d5c1a2e87)

</details>

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz>
2026-10-31 11:28:14 -06:00

159 lines
5.7 KiB
Dart

import 'dart:ui' show CheckedState, Tristate;
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:buzz/shared/theme/theme.dart';
import 'package:buzz/shared/widgets/buzz_setting_menu.dart';
void main() {
Widget menu({required bool enabled}) => MaterialApp(
theme: AppTheme.light(),
home: Scaffold(
body: BuzzSettingMenu<String>(
controlKey: const Key('menu'),
enabled: enabled,
label: 'Effort',
onSelected: (_) {},
options: const [
BuzzSettingMenuOption(key: Key('low'), label: 'Low', value: 'low'),
BuzzSettingMenuOption(key: Key('high'), label: 'High', value: 'high'),
],
value: 'high',
valueLabel: 'High',
),
),
);
testWidgets('exposes button, expanded and checked semantics', (tester) async {
final handle = tester.ensureSemantics();
await tester.pumpWidget(menu(enabled: true));
SemanticsData trigger() =>
tester.getSemantics(find.byKey(const Key('menu'))).getSemanticsData();
expect(trigger().flagsCollection.isButton, isTrue);
expect(trigger().flagsCollection.isEnabled, Tristate.isTrue);
expect(trigger().flagsCollection.isExpanded, Tristate.isFalse);
await tester.tap(find.byKey(const Key('menu')));
await tester.pumpAndSettle();
expect(trigger().flagsCollection.isExpanded, Tristate.isTrue);
SemanticsData option(String key) =>
tester.getSemantics(find.byKey(Key(key))).getSemanticsData();
expect(option('high').flagsCollection.isChecked, CheckedState.isTrue);
expect(option('low').flagsCollection.isChecked, CheckedState.isFalse);
handle.dispose();
});
testWidgets('a disabled menu reads as a disabled button', (tester) async {
final handle = tester.ensureSemantics();
await tester.pumpWidget(menu(enabled: false));
final data = tester
.getSemantics(find.byKey(const Key('menu')))
.getSemanticsData();
expect(data.flagsCollection.isButton, isTrue);
expect(data.flagsCollection.isEnabled, Tristate.isFalse);
handle.dispose();
});
testWidgets('iOS grouped choices use the native menu and update selection', (
tester,
) async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
addTearDown(() => debugDefaultTargetPlatformOverride = null);
const channel = MethodChannel('buzz/navigation_glass/987');
final calls = <MethodCall>[];
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(channel, (
call,
) async {
calls.add(call);
return null;
});
addTearDown(
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
channel,
null,
),
);
String? selected;
Widget nativeMenu({bool enabled = true, String value = 'high'}) =>
MaterialApp(
home: Scaffold(
body: BuzzSettingMenu<String>(
controlKey: const Key('native-menu'),
grouped: true,
enabled: enabled,
label: 'Thinking',
onSelected: (value) => selected = value,
options: const [
BuzzSettingMenuOption(label: 'Low', value: 'low'),
BuzzSettingMenuOption(label: 'High', value: 'high'),
],
value: value,
valueLabel: value,
),
),
);
await tester.binding.setSurfaceSize(const Size(340, 800));
addTearDown(() => tester.binding.setSurfaceSize(null));
await tester.pumpWidget(nativeMenu(enabled: false));
final view = tester.widget<UiKitView>(find.byType(UiKitView));
expect(view.viewType, 'buzz/navigation_glass');
expect(view.creationParams, containsPair('deferMenuActions', false));
expect(view.creationParams, containsPair('fillWidth', true));
expect(view.gestureRecognizers, isEmpty);
expect(find.byType(MenuAnchor), findsNothing);
expect(
view.creationParams,
containsPair('accessibilityLabel', 'Thinking: high'),
);
expect(
((view.creationParams as Map)['menu'] as List)[1],
containsPair('checked', true),
);
view.onPlatformViewCreated!(987);
await tester.pump();
Future<void> choose(int index) async {
await tester.binding.defaultBinaryMessenger.handlePlatformMessage(
channel.name,
const StandardMethodCodec().encodeMethodCall(
MethodCall('menuAction', index),
),
(_) {},
);
await tester.pump();
}
await tester.pumpWidget(nativeMenu());
await tester.pump();
final portraitWidth = tester.getSize(find.byType(UiKitView)).width;
await tester.binding.setSurfaceSize(const Size(600, 340));
await tester.pump();
expect(
tester.getSize(find.byType(UiKitView)).width,
greaterThan(portraitWidth),
);
await choose(0);
expect(selected, 'low');
await tester.pumpWidget(nativeMenu(value: 'low'));
await tester.pump();
final content =
calls.lastWhere((call) => call.method == 'setContent').arguments as Map;
expect((content['menu'] as List)[0], containsPair('checked', true));
await tester.pumpWidget(nativeMenu(enabled: false));
await tester.pump();
expect(
calls.lastWhere((call) => call.method == 'setAppearance').arguments,
containsPair('enabled', false),
);
await choose(1);
expect(selected, 'low');
await tester.pumpWidget(nativeMenu(value: 'low'));
await tester.pump();
await choose(1);
expect(selected, 'high');
await tester.pumpWidget(const SizedBox.shrink());
debugDefaultTargetPlatformOverride = null;
});
}