Files
IKAR/test/voice_transcription_test.dart
T
sevenhill 1ef2b7420e
Tests / test (push) Canceled after 0s
Build Android (FCM) / build-android-fcm (push) Canceled after 0s
Build Android / build-android (push) Canceled after 0s
Build iOS / build-ios (push) Canceled after 0s
Build Linux / build-linux (push) Canceled after 0s
Build macOS / build-macos (push) Canceled after 0s
Build Windows / build-windows (push) Canceled after 0s
Native crypto / native-crypto (push) Canceled after 0s
Release (main) / android (oneme) (push) Canceled after 0s
Release (main) / android (qlyra) (push) Canceled after 0s
Release (main) / windows (push) Canceled after 0s
Release (main) / linux (push) Canceled after 0s
Release (main) / macos (push) Canceled after 0s
Release (main) / ios (push) Canceled after 0s
Release (main) / release (push) Canceled after 0s
Port Komet UI improvements and implement IKAR ghost mode
Adapt video editing, decrypted message actions, custom themes and gestures from PR 166. Track visible server presence per session and preserve delivery for invisible clients.
2026-10-07 22:07:05 +03:00

174 lines
5.4 KiB
Dart

import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:ikar/backend/modules/messages.dart';
import 'package:ikar/core/media/voice_transcription.dart';
import 'package:ikar/models/attachment.dart';
CachedMessage _message({
String? status = 'sent',
bool deleted = false,
List<MessageAttachment>? attachments,
}) => CachedMessage(
id: '101',
accountId: 1,
chatId: 10,
senderId: 7,
time: 1,
status: status,
deleted: deleted,
attachments: attachments ?? const [AudioAttachment(audioId: 42)],
);
void main() {
setUp(TranscriptionCache.clear);
tearDown(TranscriptionCache.clear);
test('voice target retains the original IDs of a forwarded recording', () {
final target = VoiceTranscriptionTarget.fromMessage(
_message(
attachments: const [
ForwardedMessageAttachment(
originalSenderId: 9,
originalChatId: 20,
originalMessageId: '202',
originalAttachments: [AudioAttachment(audioId: 43)],
),
],
),
)!;
expect((target.chatId, target.messageId, target.audioId), (20, '202', 43));
});
test(
'text, videos, files and unsent voices have no transcription action',
() {
for (final attachments in <List<MessageAttachment>>[
[],
[const VideoAttachment(videoId: 42)],
[const FileAttachment(fileId: 42, name: 'audio.mp3')],
[const AudioAttachment()],
]) {
expect(
VoiceTranscriptionTarget.fromMessage(
_message(attachments: attachments),
),
isNull,
);
}
for (final status in ['sending', 'pending', 'error']) {
expect(
VoiceTranscriptionTarget.fromMessage(_message(status: status)),
isNull,
);
}
expect(
VoiceTranscriptionTarget.fromMessage(_message(deleted: true)),
isNull,
);
},
);
test(
'request shows progress, deduplicates taps and displays result',
() async {
final target = VoiceTranscriptionTarget.fromMessage(_message())!;
final response = Completer<TranscriptionResult>();
var requests = 0;
Future<TranscriptionResult> request(int chat, int message, int audio) {
expect((chat, message, audio), (10, 101, 42));
requests++;
return response.future;
}
final first = VoiceTranscription.toggle(target, request);
expect(TranscriptionCache.get('101')?.status, 0);
expect(TranscriptionCache.isExpanded('101'), isTrue);
await VoiceTranscription.toggle(target, request);
expect(requests, 1);
response.complete(TranscriptionResult(status: 1, text: 'Привет'));
await first;
expect(TranscriptionCache.get('101')?.text, 'Привет');
},
);
test(
'completed text hides and reopens without a new server request',
() async {
final target = VoiceTranscriptionTarget.fromMessage(_message())!;
TranscriptionCache.put(
'101',
TranscriptionResult(status: 1, text: 'Привет'),
expanded: true,
);
var changes = 0;
void listener() => changes++;
TranscriptionCache.listen('101', listener);
addTearDown(() => TranscriptionCache.unlisten('101', listener));
Future<TranscriptionResult> noRequest(
int chat,
int message,
int audio,
) async {
fail('Cached transcription must not request the server');
}
await VoiceTranscription.toggle(target, noRequest);
expect(TranscriptionCache.isExpanded('101'), isFalse);
await VoiceTranscription.toggle(target, noRequest);
expect(TranscriptionCache.isExpanded('101'), isTrue);
expect(changes, 2);
},
);
test('failed recognition can be retried', () async {
final target = VoiceTranscriptionTarget.fromMessage(_message())!;
await VoiceTranscription.toggle(
target,
(_, _, _) async => throw StateError('offline'),
);
expect(TranscriptionCache.get('101')?.status, -1);
await VoiceTranscription.toggle(
target,
(_, _, _) async => TranscriptionResult(status: 1, text: 'Повтор'),
);
expect(TranscriptionCache.get('101')?.text, 'Повтор');
});
test('a queued response cannot replace the completed push result', () async {
final target = VoiceTranscriptionTarget.fromMessage(_message())!;
final response = Completer<TranscriptionResult>();
final pending = VoiceTranscription.toggle(
target,
(_, _, _) => response.future,
);
TranscriptionCache.put(
'101',
TranscriptionResult(status: 1, text: 'Готово'),
expanded: true,
);
response.complete(TranscriptionResult(status: 0));
await pending;
expect(TranscriptionCache.get('101')?.text, 'Готово');
expect(TranscriptionCache.get('101')?.status, 1);
});
test('a late request error cannot erase a completed push result', () async {
final target = VoiceTranscriptionTarget.fromMessage(_message())!;
final response = Completer<TranscriptionResult>();
final pending = VoiceTranscription.toggle(
target,
(_, _, _) => response.future,
);
TranscriptionCache.put(
'101',
TranscriptionResult(status: 1, text: 'Готово'),
expanded: true,
);
response.completeError(StateError('connection lost after push'));
await pending;
expect(TranscriptionCache.get('101')?.text, 'Готово');
expect(TranscriptionCache.get('101')?.status, 1);
});
}