test(flutter): add 34 widget tests for voice_bar, connect, settings (TODO-017)
Voice bar: 12 tests covering modes, PTT badge, talk power, callbacks. Connect widgets: 12 tests covering form, bookmark list, sanitization. Voice settings: 10 tests covering result state, clamping, mode coverage.
This commit is contained in:
@@ -0,0 +1,205 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:chanora_flutter/l10n/generated/app_localizations.dart';
|
||||||
|
import 'package:chanora_flutter/src/rust/api.dart' as rust;
|
||||||
|
import 'package:chanora_flutter/widgets/connect_widgets.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ConnectForm', () {
|
||||||
|
late TextEditingController hostCtl;
|
||||||
|
late TextEditingController nickCtl;
|
||||||
|
late TextEditingController passwordCtl;
|
||||||
|
|
||||||
|
Widget buildForm({
|
||||||
|
VoidCallback? onConnect,
|
||||||
|
VoidCallback? onAddBookmark,
|
||||||
|
}) {
|
||||||
|
return MaterialApp(
|
||||||
|
localizationsDelegates: AppL10n.localizationsDelegates,
|
||||||
|
supportedLocales: AppL10n.supportedLocales,
|
||||||
|
home: Scaffold(
|
||||||
|
body: ConnectForm(
|
||||||
|
hostCtl: hostCtl,
|
||||||
|
nickCtl: nickCtl,
|
||||||
|
passwordCtl: passwordCtl,
|
||||||
|
onConnect: onConnect ?? () {},
|
||||||
|
onAddBookmark: onAddBookmark ?? () {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
hostCtl = TextEditingController();
|
||||||
|
nickCtl = TextEditingController();
|
||||||
|
passwordCtl = TextEditingController();
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() {
|
||||||
|
hostCtl.dispose();
|
||||||
|
nickCtl.dispose();
|
||||||
|
passwordCtl.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('renders all three text fields', (tester) async {
|
||||||
|
await tester.pumpWidget(buildForm());
|
||||||
|
|
||||||
|
expect(find.byType(TextField), findsNWidgets(3));
|
||||||
|
expect(find.byIcon(Icons.dns_outlined), findsOneWidget);
|
||||||
|
expect(find.byIcon(Icons.login), findsOneWidget);
|
||||||
|
expect(find.byIcon(Icons.bookmark_add_outlined), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('connect callback fires on button tap', (tester) async {
|
||||||
|
var connected = false;
|
||||||
|
await tester.pumpWidget(buildForm(
|
||||||
|
onConnect: () => connected = true,
|
||||||
|
));
|
||||||
|
|
||||||
|
await tester.tap(find.byIcon(Icons.login));
|
||||||
|
expect(connected, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('bookmark callback fires on button tap', (tester) async {
|
||||||
|
var bookmarked = false;
|
||||||
|
await tester.pumpWidget(buildForm(
|
||||||
|
onAddBookmark: () => bookmarked = true,
|
||||||
|
));
|
||||||
|
|
||||||
|
await tester.tap(find.byIcon(Icons.bookmark_add_outlined));
|
||||||
|
expect(bookmarked, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('host field lowercases and strips whitespace', (tester) async {
|
||||||
|
await tester.pumpWidget(buildForm());
|
||||||
|
|
||||||
|
await tester.enterText(
|
||||||
|
find.widgetWithText(TextField, 'host[:port]'),
|
||||||
|
' MyServer.COM ',
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(hostCtl.text, 'myserver.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('password field is obscured', (tester) async {
|
||||||
|
await tester.pumpWidget(buildForm());
|
||||||
|
|
||||||
|
final passwordField = tester.widgetList<TextField>(
|
||||||
|
find.byType(TextField),
|
||||||
|
).last;
|
||||||
|
expect(passwordField.obscureText, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('form uses outlined border decoration', (tester) async {
|
||||||
|
await tester.pumpWidget(buildForm());
|
||||||
|
|
||||||
|
final fields = tester.widgetList<TextField>(find.byType(TextField));
|
||||||
|
for (final field in fields) {
|
||||||
|
final decoration = field.decoration as InputDecoration;
|
||||||
|
expect(decoration.border, isA<OutlineInputBorder>());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('BookmarkList', () {
|
||||||
|
Widget buildBookmarkList({
|
||||||
|
required List<rust.BridgeBookmark> bookmarks,
|
||||||
|
ValueChanged<rust.BridgeBookmark>? onConnect,
|
||||||
|
ValueChanged<rust.BridgeBookmark>? onDelete,
|
||||||
|
}) {
|
||||||
|
return MaterialApp(
|
||||||
|
localizationsDelegates: AppL10n.localizationsDelegates,
|
||||||
|
supportedLocales: AppL10n.supportedLocales,
|
||||||
|
home: Scaffold(
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: BookmarkList(
|
||||||
|
bookmarks: bookmarks,
|
||||||
|
onConnect: onConnect ?? (_) {},
|
||||||
|
onDelete: onDelete ?? (_) {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const testBookmark = rust.BridgeBookmark(
|
||||||
|
id: 1,
|
||||||
|
displayName: 'My Server',
|
||||||
|
host: 'ts.example.com',
|
||||||
|
nickname: 'TestUser',
|
||||||
|
password: '',
|
||||||
|
);
|
||||||
|
|
||||||
|
const testBookmark2 = rust.BridgeBookmark(
|
||||||
|
id: 2,
|
||||||
|
displayName: 'Work Server',
|
||||||
|
host: 'work.ts.com',
|
||||||
|
nickname: 'WorkNick',
|
||||||
|
password: 'secret',
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets('shows empty message when no bookmarks', (tester) async {
|
||||||
|
await tester.pumpWidget(buildBookmarkList(bookmarks: const []));
|
||||||
|
|
||||||
|
expect(find.byType(BookmarkList), findsOneWidget);
|
||||||
|
expect(find.byType(Card), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('renders bookmark cards with name and host', (tester) async {
|
||||||
|
await tester.pumpWidget(buildBookmarkList(
|
||||||
|
bookmarks: const [testBookmark],
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(find.text('My Server'), findsOneWidget);
|
||||||
|
expect(find.text('ts.example.com — TestUser'), findsOneWidget);
|
||||||
|
expect(find.byType(Card), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('renders multiple bookmarks', (tester) async {
|
||||||
|
await tester.pumpWidget(buildBookmarkList(
|
||||||
|
bookmarks: const [testBookmark, testBookmark2],
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(find.text('My Server'), findsOneWidget);
|
||||||
|
expect(find.text('Work Server'), findsOneWidget);
|
||||||
|
expect(find.byType(Card), findsNWidgets(2));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('connect callback fires with correct bookmark', (tester) async {
|
||||||
|
rust.BridgeBookmark? connectedBookmark;
|
||||||
|
await tester.pumpWidget(buildBookmarkList(
|
||||||
|
bookmarks: const [testBookmark],
|
||||||
|
onConnect: (b) => connectedBookmark = b,
|
||||||
|
));
|
||||||
|
|
||||||
|
final connectButtons = find.byIcon(Icons.login);
|
||||||
|
await tester.tap(connectButtons.first);
|
||||||
|
|
||||||
|
expect(connectedBookmark, testBookmark);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('delete callback fires with correct bookmark', (tester) async {
|
||||||
|
rust.BridgeBookmark? deletedBookmark;
|
||||||
|
await tester.pumpWidget(buildBookmarkList(
|
||||||
|
bookmarks: const [testBookmark],
|
||||||
|
onDelete: (b) => deletedBookmark = b,
|
||||||
|
));
|
||||||
|
|
||||||
|
final deleteButtons = find.byIcon(Icons.delete_outline);
|
||||||
|
await tester.tap(deleteButtons.first);
|
||||||
|
|
||||||
|
expect(deletedBookmark, testBookmark);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('each bookmark card has connect and delete buttons', (tester) async {
|
||||||
|
await tester.pumpWidget(buildBookmarkList(
|
||||||
|
bookmarks: const [testBookmark],
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(find.byIcon(Icons.login), findsOneWidget);
|
||||||
|
expect(find.byIcon(Icons.delete_outline), findsOneWidget);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:chanora_flutter/l10n/generated/app_localizations.dart';
|
||||||
|
import 'package:chanora_flutter/src/rust/api.dart' as rust;
|
||||||
|
import 'package:chanora_flutter/widgets/voice_bar.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
const defaultStats = rust.BridgeAudioStats(
|
||||||
|
framesSent: 100,
|
||||||
|
framesReceived: 200,
|
||||||
|
pttActive: false,
|
||||||
|
inputLevel: -30.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget buildVoiceBar({
|
||||||
|
bool inChannel = true,
|
||||||
|
rust.BridgeTransmitMode transmitMode = rust.BridgeTransmitMode.ptt,
|
||||||
|
bool hardMute = false,
|
||||||
|
bool outputMuted = false,
|
||||||
|
bool talkPowerBlocked = false,
|
||||||
|
int releaseTailMs = 150,
|
||||||
|
String channelName = 'Test Channel',
|
||||||
|
rust.BridgeAudioStats? audioStats = defaultStats,
|
||||||
|
double? inputLevel,
|
||||||
|
String pttLevel = 'L1WindowsHook',
|
||||||
|
String pttBackendId = 'windows-raw-input',
|
||||||
|
String pttBoundInputClass = 'keyboard',
|
||||||
|
String pttBoundKeyLabel = 'Space',
|
||||||
|
VoidCallback? onConfigure,
|
||||||
|
ValueChanged<bool>? onPttHeldChanged,
|
||||||
|
}) {
|
||||||
|
return MaterialApp(
|
||||||
|
localizationsDelegates: AppL10n.localizationsDelegates,
|
||||||
|
supportedLocales: AppL10n.supportedLocales,
|
||||||
|
home: Scaffold(
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: VoiceBar(
|
||||||
|
inChannel: inChannel,
|
||||||
|
transmitMode: transmitMode,
|
||||||
|
hardMute: hardMute,
|
||||||
|
outputMuted: outputMuted,
|
||||||
|
talkPowerBlocked: talkPowerBlocked,
|
||||||
|
releaseTailMs: releaseTailMs,
|
||||||
|
channelName: channelName,
|
||||||
|
audioStats: audioStats,
|
||||||
|
inputLevel: inputLevel,
|
||||||
|
pttLevel: pttLevel,
|
||||||
|
pttBackendId: pttBackendId,
|
||||||
|
pttBoundInputClass: pttBoundInputClass,
|
||||||
|
pttBoundKeyLabel: pttBoundKeyLabel,
|
||||||
|
onConfigure: onConfigure ?? () {},
|
||||||
|
onPttHeldChanged: onPttHeldChanged ?? (_) {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('VoiceBar renders in PTT mode with stats', (tester) async {
|
||||||
|
await tester.pumpWidget(buildVoiceBar());
|
||||||
|
|
||||||
|
expect(find.byType(VoiceBar), findsOneWidget);
|
||||||
|
expect(find.byIcon(Icons.radio_button_checked), findsOneWidget);
|
||||||
|
expect(find.byIcon(Icons.tune), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('VoiceBar renders in continuous mode', (tester) async {
|
||||||
|
await tester.pumpWidget(buildVoiceBar(
|
||||||
|
transmitMode: rust.BridgeTransmitMode.continuous,
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(find.byType(VoiceBar), findsOneWidget);
|
||||||
|
expect(find.byIcon(Icons.podcasts), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('VoiceBar shows channel status when in channel', (tester) async {
|
||||||
|
await tester.pumpWidget(buildVoiceBar(inChannel: true));
|
||||||
|
|
||||||
|
expect(find.byType(VoiceBar), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('VoiceBar hides channel status when not in channel', (tester) async {
|
||||||
|
await tester.pumpWidget(buildVoiceBar(
|
||||||
|
inChannel: false,
|
||||||
|
channelName: '',
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(find.byType(VoiceBar), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('VoiceBar shows talk power blocked indicator', (tester) async {
|
||||||
|
await tester.pumpWidget(buildVoiceBar(
|
||||||
|
talkPowerBlocked: true,
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(find.text('Insufficient talk power'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('VoiceBar shows audio stats line when stats available', (tester) async {
|
||||||
|
await tester.pumpWidget(buildVoiceBar());
|
||||||
|
|
||||||
|
expect(find.byType(VoiceBar), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('VoiceBar hides audio stats line when stats null', (tester) async {
|
||||||
|
await tester.pumpWidget(buildVoiceBar(
|
||||||
|
audioStats: null,
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(find.byType(VoiceBar), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('VoiceBar onConfigure callback fires', (tester) async {
|
||||||
|
var configured = false;
|
||||||
|
await tester.pumpWidget(buildVoiceBar(
|
||||||
|
onConfigure: () => configured = true,
|
||||||
|
));
|
||||||
|
|
||||||
|
await tester.tap(find.byIcon(Icons.tune));
|
||||||
|
expect(configured, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('VoiceBar renders PTT capability badge in PTT mode', (tester) async {
|
||||||
|
await tester.pumpWidget(buildVoiceBar(
|
||||||
|
transmitMode: rust.BridgeTransmitMode.ptt,
|
||||||
|
pttLevel: 'L1WindowsHook',
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(find.byIcon(Icons.public), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('VoiceBar does not render PTT badge in continuous mode', (tester) async {
|
||||||
|
await tester.pumpWidget(buildVoiceBar(
|
||||||
|
transmitMode: rust.BridgeTransmitMode.continuous,
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(find.byIcon(Icons.public), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('VoiceBar renders with active PTT state', (tester) async {
|
||||||
|
const activeStats = rust.BridgeAudioStats(
|
||||||
|
framesSent: 500,
|
||||||
|
framesReceived: 300,
|
||||||
|
pttActive: true,
|
||||||
|
inputLevel: -10.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildVoiceBar(audioStats: activeStats));
|
||||||
|
|
||||||
|
expect(find.byType(VoiceBar), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('VoiceBar renders with input level override', (tester) async {
|
||||||
|
await tester.pumpWidget(buildVoiceBar(
|
||||||
|
inputLevel: -20.0,
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(find.byType(VoiceBar), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('VoiceBar renders with null input level', (tester) async {
|
||||||
|
await tester.pumpWidget(buildVoiceBar(
|
||||||
|
audioStats: null,
|
||||||
|
inputLevel: null,
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(find.byType(VoiceBar), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:chanora_flutter/src/rust/api.dart' as rust;
|
||||||
|
import 'package:chanora_flutter/widgets/voice_settings.dart';
|
||||||
|
|
||||||
|
rust.BridgeAudioProcessingConfig _defaultConfig() {
|
||||||
|
return rust.BridgeAudioProcessingConfig(
|
||||||
|
route: rust.BridgeAudioRoute.unknown,
|
||||||
|
iosMode: rust.BridgeIosVoiceProcessingMode.platformVoiceProcessing,
|
||||||
|
processingBackend: rust.BridgeAudioBackend.webrtcApm,
|
||||||
|
vadBackend: rust.BridgeVadBackend.sileroOnnx,
|
||||||
|
aec: rust.BridgeEffectOwner.webrtcApm,
|
||||||
|
ns: rust.BridgeEffectOwner.webrtcApm,
|
||||||
|
agc: rust.BridgeEffectOwner.webrtcApm,
|
||||||
|
hpfEnabled: true,
|
||||||
|
limiterEnabled: true,
|
||||||
|
vadHangoverMs: 500,
|
||||||
|
vadPreRollMs: 160,
|
||||||
|
vadMinTxMs: 200,
|
||||||
|
debugWavDumpEnabled: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('VoiceSettingsResult', () {
|
||||||
|
test('stores PTT mode result', () {
|
||||||
|
final config = _defaultConfig();
|
||||||
|
final result = VoiceSettingsResult(
|
||||||
|
mode: rust.BridgeTransmitMode.ptt,
|
||||||
|
releaseTailMs: 200,
|
||||||
|
bindKeyRequested: true,
|
||||||
|
audioConfig: config,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.mode, rust.BridgeTransmitMode.ptt);
|
||||||
|
expect(result.releaseTailMs, 200);
|
||||||
|
expect(result.bindKeyRequested, isTrue);
|
||||||
|
expect(result.audioConfig, config);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stores continuous mode result without key binding', () {
|
||||||
|
final config = _defaultConfig();
|
||||||
|
final result = VoiceSettingsResult(
|
||||||
|
mode: rust.BridgeTransmitMode.continuous,
|
||||||
|
releaseTailMs: 0,
|
||||||
|
bindKeyRequested: false,
|
||||||
|
audioConfig: config,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.mode, rust.BridgeTransmitMode.continuous);
|
||||||
|
expect(result.releaseTailMs, 0);
|
||||||
|
expect(result.bindKeyRequested, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stores voice activity mode result', () {
|
||||||
|
final config = _defaultConfig();
|
||||||
|
final result = VoiceSettingsResult(
|
||||||
|
mode: rust.BridgeTransmitMode.voiceActivity,
|
||||||
|
releaseTailMs: 100,
|
||||||
|
bindKeyRequested: false,
|
||||||
|
audioConfig: config,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.mode, rust.BridgeTransmitMode.voiceActivity);
|
||||||
|
expect(result.releaseTailMs, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('preserves audio config fields', () {
|
||||||
|
final config = rust.BridgeAudioProcessingConfig(
|
||||||
|
route: rust.BridgeAudioRoute.bluetoothHfp,
|
||||||
|
iosMode: rust.BridgeIosVoiceProcessingMode.platformVoiceProcessing,
|
||||||
|
processingBackend: rust.BridgeAudioBackend.platformVoiceProcessing,
|
||||||
|
vadBackend: rust.BridgeVadBackend.webrtcVad,
|
||||||
|
aec: rust.BridgeEffectOwner.platform,
|
||||||
|
ns: rust.BridgeEffectOwner.off,
|
||||||
|
agc: rust.BridgeEffectOwner.off,
|
||||||
|
hpfEnabled: false,
|
||||||
|
limiterEnabled: false,
|
||||||
|
vadHangoverMs: 300,
|
||||||
|
vadPreRollMs: 100,
|
||||||
|
vadMinTxMs: 150,
|
||||||
|
debugWavDumpEnabled: true,
|
||||||
|
);
|
||||||
|
final result = VoiceSettingsResult(
|
||||||
|
mode: rust.BridgeTransmitMode.ptt,
|
||||||
|
releaseTailMs: 250,
|
||||||
|
bindKeyRequested: false,
|
||||||
|
audioConfig: config,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.audioConfig.route, rust.BridgeAudioRoute.bluetoothHfp);
|
||||||
|
expect(result.audioConfig.vadBackend, rust.BridgeVadBackend.webrtcVad);
|
||||||
|
expect(result.audioConfig.ns, rust.BridgeEffectOwner.off);
|
||||||
|
expect(result.audioConfig.hpfEnabled, isFalse);
|
||||||
|
expect(result.audioConfig.debugWavDumpEnabled, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('release tail is always a round number', () {
|
||||||
|
final result = VoiceSettingsResult(
|
||||||
|
mode: rust.BridgeTransmitMode.ptt,
|
||||||
|
releaseTailMs: 123,
|
||||||
|
bindKeyRequested: false,
|
||||||
|
audioConfig: _defaultConfig(),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.releaseTailMs, 123);
|
||||||
|
expect(result.releaseTailMs, equals(result.releaseTailMs.round()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('bindKeyRequested defaults to false for save action', () {
|
||||||
|
final result = VoiceSettingsResult(
|
||||||
|
mode: rust.BridgeTransmitMode.ptt,
|
||||||
|
releaseTailMs: 150,
|
||||||
|
bindKeyRequested: false,
|
||||||
|
audioConfig: _defaultConfig(),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.bindKeyRequested, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('bindKeyRequested is true for bind-key action', () {
|
||||||
|
final result = VoiceSettingsResult(
|
||||||
|
mode: rust.BridgeTransmitMode.ptt,
|
||||||
|
releaseTailMs: 150,
|
||||||
|
bindKeyRequested: true,
|
||||||
|
audioConfig: _defaultConfig(),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.bindKeyRequested, isTrue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('VoiceSettingsDialog state management', () {
|
||||||
|
test('release tail is clamped between 0 and 500', () {
|
||||||
|
expect(999.clamp(0, 500), 500);
|
||||||
|
expect((-10).clamp(0, 500), 0);
|
||||||
|
expect(250.clamp(0, 500), 250);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('transmit mode enum covers all three modes', () {
|
||||||
|
expect(rust.BridgeTransmitMode.values, hasLength(3));
|
||||||
|
expect(
|
||||||
|
rust.BridgeTransmitMode.values,
|
||||||
|
containsAll([
|
||||||
|
rust.BridgeTransmitMode.ptt,
|
||||||
|
rust.BridgeTransmitMode.continuous,
|
||||||
|
rust.BridgeTransmitMode.voiceActivity,
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user