Reduce Linux setup ambiguity and surface desktop input/message failures honestly
Clarify ONNX Runtime guidance with direct-open install hints, restore desktop WebRTC VAD visibility, map mouse side buttons through focused PTT capture/runtime paths, and wait for server acks before showing chat sends as successful. Constraint: Linux release UX must stay functional when ONNX Runtime is optional and GNOME portal availability varies Rejected: Keep desktop VAD locked to Silero only | misleads users when ONNX Runtime is skipped Confidence: medium Scope-risk: moderate Directive: Preserve the protocol send-ack wait path for chat so UI success always tracks real server acceptance Tested: flutter analyze lib/main.dart lib/widgets/chat_views.dart lib/widgets/input_dialogs.dart lib/widgets/startup_dependency_screen.dart; flutter test test/widgets/input_dialogs_test.dart test/widgets/chat_views_test.dart test/services/startup_dependency_check_test.dart test/widgets/startup_dependency_screen_test.dart test/widgets/voice_settings_controls_test.dart test/widgets/audio_processing_config_state_test.dart; cargo test -p chanora_protocol --lib; cargo test -p chanora_audio ptt_backends --lib Not-tested: Live manual GNOME portal rebind/global PTT on a real desktop session; observer-bot chat against a live server after the sender-name fallback change
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:chanora_flutter/services/startup_dependency_check.dart';
|
||||
|
||||
void main() {
|
||||
tearDown(() {
|
||||
debugResetStartupDependencyCheck();
|
||||
});
|
||||
|
||||
test('non-linux hosts skip startup dependency issues', () async {
|
||||
debugResetStartupDependencyCheck(platformIsLinux: () => false);
|
||||
|
||||
final result = await checkStartupDependencies();
|
||||
|
||||
expect(result.issues, isEmpty);
|
||||
});
|
||||
|
||||
test('missing SDL2 is reported as required on Debian-like systems', () async {
|
||||
debugResetStartupDependencyCheck(
|
||||
platformIsLinux: () => true,
|
||||
libraryProbe: (candidate) => false,
|
||||
fileExists: (_) async => false,
|
||||
osReleaseProvider: () async => 'ID=ubuntu\nID_LIKE=debian\n',
|
||||
resolvedExecutableProvider: () => '/opt/chanora/chanora_flutter',
|
||||
currentDirectoryProvider: () => '/tmp',
|
||||
);
|
||||
|
||||
final result = await checkStartupDependencies();
|
||||
|
||||
expect(result.hasIssues, isTrue);
|
||||
final sdl = result.issues.firstWhere(
|
||||
(issue) => issue.id == 'linux-sdl2-runtime',
|
||||
);
|
||||
expect(sdl.isRequired, isTrue);
|
||||
expect(sdl.installHints.single.command, 'sudo apt install libsdl2-2.0-0');
|
||||
});
|
||||
|
||||
test(
|
||||
'missing ONNX runtime is reported as recommended when SDL2 is present',
|
||||
() async {
|
||||
debugResetStartupDependencyCheck(
|
||||
platformIsLinux: () => true,
|
||||
libraryProbe: (candidate) => candidate.contains('SDL2'),
|
||||
fileExists: (_) async => false,
|
||||
osReleaseProvider: () async => 'ID=fedora\n',
|
||||
resolvedExecutableProvider: () => '/opt/chanora/chanora_flutter',
|
||||
currentDirectoryProvider: () => '/tmp',
|
||||
currentAbiProvider: () => Abi.linuxX64,
|
||||
);
|
||||
|
||||
final result = await checkStartupDependencies();
|
||||
|
||||
expect(result.issues, hasLength(1));
|
||||
final ort = result.issues.single;
|
||||
expect(ort.id, 'linux-onnxruntime');
|
||||
expect(ort.isRequired, isFalse);
|
||||
expect(
|
||||
ort.installHints.any(
|
||||
(hint) =>
|
||||
hint.command ==
|
||||
'https://github.com/microsoft/onnxruntime/releases',
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
ort.installHints.any(
|
||||
(hint) => hint.command == 'onnxruntime-linux-x64-<version>.tgz',
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
ort.installHints.any((hint) => hint.command.contains('ORT_DYLIB_PATH')),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
ort.details,
|
||||
contains(
|
||||
'This machine needs the Linux x64 CPU archive (onnxruntime-linux-x64-<version>.tgz).',
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('bundle-local ONNX runtime clears the recommendation', () async {
|
||||
debugResetStartupDependencyCheck(
|
||||
platformIsLinux: () => true,
|
||||
libraryProbe: (candidate) => candidate.contains('SDL2'),
|
||||
fileExists: (path) async => path == '/opt/chanora/lib/libonnxruntime.so',
|
||||
resolvedExecutableProvider: () => '/opt/chanora/chanora_flutter',
|
||||
currentDirectoryProvider: () => '/tmp',
|
||||
);
|
||||
|
||||
final result = await checkStartupDependencies();
|
||||
|
||||
expect(result.issues, isEmpty);
|
||||
});
|
||||
|
||||
test('logging missing startup dependencies appends a log line', () async {
|
||||
final tempDir = await Directory.systemTemp.createTemp(
|
||||
'chanora-startup-log',
|
||||
);
|
||||
addTearDown(() => tempDir.delete(recursive: true));
|
||||
final logFile = File('${tempDir.path}/chanora.log');
|
||||
|
||||
debugResetStartupDependencyCheck(logFilePathProvider: () => logFile.path);
|
||||
|
||||
const result = StartupDependencyCheckResult(
|
||||
platformLabel: 'Fedora',
|
||||
issues: [
|
||||
StartupDependencyIssue(
|
||||
id: 'linux-sdl2-runtime',
|
||||
title: 'SDL2 runtime is missing',
|
||||
summary: 'Audio playback needs SDL2.',
|
||||
details: ['Install SDL2 and recheck.'],
|
||||
severity: StartupDependencySeverity.required,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
await logStartupDependencyIssues(result);
|
||||
|
||||
final text = await logFile.readAsString();
|
||||
expect(text, contains('[startup_dependency_check]'));
|
||||
expect(text, contains('platform="Fedora"'));
|
||||
expect(
|
||||
text,
|
||||
contains('issues=[linux-sdl2-runtime:required:SDL2 runtime is missing]'),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -31,14 +31,14 @@ void main() {
|
||||
expect(state.agcEnabled, isTrue);
|
||||
});
|
||||
|
||||
test('normalizes desktop VAD backend to Silero', () {
|
||||
test('desktop VAD normalization keeps explicit WebRTC selections', () {
|
||||
expect(
|
||||
normalizedVadBackend(
|
||||
rust.BridgeVadBackend.webrtcVad,
|
||||
isWindows: true,
|
||||
isLinux: false,
|
||||
),
|
||||
rust.BridgeVadBackend.sileroOnnx,
|
||||
rust.BridgeVadBackend.webrtcVad,
|
||||
);
|
||||
expect(
|
||||
normalizedVadBackend(
|
||||
@@ -46,6 +46,14 @@ void main() {
|
||||
isWindows: false,
|
||||
isLinux: true,
|
||||
),
|
||||
rust.BridgeVadBackend.webrtcVad,
|
||||
);
|
||||
expect(
|
||||
normalizedVadBackend(
|
||||
rust.BridgeVadBackend.energyDebug,
|
||||
isWindows: true,
|
||||
isLinux: false,
|
||||
),
|
||||
rust.BridgeVadBackend.sileroOnnx,
|
||||
);
|
||||
});
|
||||
@@ -103,6 +111,7 @@ void main() {
|
||||
|
||||
test('builds Windows/Linux software WebRTC APM config consistently', () {
|
||||
final state = AudioProcessingConfigState.fromConfig(baseConfig)
|
||||
..vadBackend = rust.BridgeVadBackend.webrtcVad
|
||||
..nsEnabled = true
|
||||
..aecEnabled = false
|
||||
..agcEnabled = true;
|
||||
@@ -127,7 +136,7 @@ void main() {
|
||||
|
||||
for (final config in [windowsConfig, linuxConfig]) {
|
||||
expect(config.processingBackend, rust.BridgeAudioBackend.webrtcApm);
|
||||
expect(config.vadBackend, rust.BridgeVadBackend.sileroOnnx);
|
||||
expect(config.vadBackend, rust.BridgeVadBackend.webrtcVad);
|
||||
expect(config.aec, rust.BridgeEffectOwner.off);
|
||||
expect(config.ns, rust.BridgeEffectOwner.webrtcApm);
|
||||
expect(config.agc, rust.BridgeEffectOwner.webrtcApm);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:chanora_flutter/widgets/input_dialogs.dart';
|
||||
|
||||
void main() {
|
||||
test('browser back and forward logical keys map to mouse side bindings', () {
|
||||
expect(
|
||||
pttMouseSideButtonPlatformKeyForLogicalKey(
|
||||
LogicalKeyboardKey.browserBack,
|
||||
),
|
||||
'mouse-side-button:8',
|
||||
);
|
||||
expect(
|
||||
pttMouseSideButtonPlatformKeyForLogicalKey(LogicalKeyboardKey.goBack),
|
||||
'mouse-side-button:8',
|
||||
);
|
||||
expect(
|
||||
pttMouseSideButtonPlatformKeyForLogicalKey(
|
||||
LogicalKeyboardKey.browserForward,
|
||||
),
|
||||
'mouse-side-button:16',
|
||||
);
|
||||
});
|
||||
|
||||
test('pointer button bitmasks map to mouse side bindings', () {
|
||||
expect(
|
||||
pttMouseSideButtonPlatformKeyForButtons(0x08),
|
||||
'mouse-side-button:8',
|
||||
);
|
||||
expect(
|
||||
pttMouseSideButtonPlatformKeyForButtons(0x10),
|
||||
'mouse-side-button:16',
|
||||
);
|
||||
expect(
|
||||
pttMouseSideButtonPlatformKeyForButtons(0x18),
|
||||
'mouse-side-button:8',
|
||||
);
|
||||
expect(pttMouseSideButtonPlatformKeyForButtons(0x00), isNull);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:chanora_flutter/services/startup_dependency_check.dart';
|
||||
import 'package:chanora_flutter/widgets/startup_dependency_screen.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('startup gate shows install-help screen and can continue', (
|
||||
tester,
|
||||
) async {
|
||||
tester.view.physicalSize = const Size(1200, 1800);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
|
||||
final result = StartupDependencyCheckResult(
|
||||
platformLabel: 'Fedora',
|
||||
issues: const [
|
||||
StartupDependencyIssue(
|
||||
id: 'linux-sdl2-runtime',
|
||||
title: 'SDL2 runtime is missing',
|
||||
summary: 'Audio playback needs SDL2.',
|
||||
details: ['Install SDL2 and recheck.'],
|
||||
severity: StartupDependencySeverity.required,
|
||||
installHints: [
|
||||
StartupInstallHint(
|
||||
label: 'Release downloads',
|
||||
command: 'https://github.com/microsoft/onnxruntime/releases',
|
||||
),
|
||||
StartupInstallHint(
|
||||
label: 'Fedora',
|
||||
command: 'sudo dnf install SDL2',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
final loggedResults = <StartupDependencyCheckResult>[];
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: StartupDependencyGate(
|
||||
checker: () async => result,
|
||||
logger: (value) async {
|
||||
loggedResults.add(value);
|
||||
},
|
||||
child: const Scaffold(body: Text('ready')),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Finish Linux setup'), findsOneWidget);
|
||||
expect(find.text('SDL2 runtime is missing'), findsOneWidget);
|
||||
expect(find.text('Continue with limited mode'), findsOneWidget);
|
||||
expect(find.text('Open'), findsOneWidget);
|
||||
expect(find.text('Copy'), findsOneWidget);
|
||||
expect(loggedResults, [result]);
|
||||
|
||||
await tester.ensureVisible(find.text('Continue with limited mode'));
|
||||
await tester.tap(find.text('Continue with limited mode'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('ready'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -24,8 +24,9 @@ void main() {
|
||||
]);
|
||||
});
|
||||
|
||||
test('desktop VAD segments expose Silero as the primary backend', () {
|
||||
test('desktop VAD segments expose both Silero and WebRTC', () {
|
||||
expect(desktopVadBackendSegments.map((s) => s.value), [
|
||||
rust.BridgeVadBackend.webrtcVad,
|
||||
rust.BridgeVadBackend.sileroOnnx,
|
||||
]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user