fix(#235): render inline @[Name] mention chips + reply-gifs
Two shared-root render gaps (message tokens were only matched at fixed positions): - TranslatedMessageContent: parse @[Name] anywhere (leading, inline, or repeated) and render each as a chip; messages without a mention keep full link support (#233). - GifHelper.parseGif: strip a leading `@[Name] ` reply prefix before matching so a reply-with-gif resolves to the gif instead of literal text; arbitrary text before a gif is still left as text (#232). Tests: parseGif reply/format cases + widget tests for inline/leading/multiple mention chips. Full suite 402 green. Part of #233, #232. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>pull/239/head
parent
b5e3d18688
commit
edd9530c25
@ -0,0 +1,43 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:meshcore_open/helpers/gif_helper.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('GifHelper.parseGif reply/format handling', () {
|
||||||
|
test('g:ID form', () {
|
||||||
|
expect(GifHelper.parseGif('g:abc123'), 'abc123');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reply-gif: a leading @[Name] prefix is ignored (#232)', () {
|
||||||
|
expect(GifHelper.parseGif('@[Bob] g:abc123'), 'abc123');
|
||||||
|
expect(GifHelper.parseGif('@[Bob Smith] g:abc123'), 'abc123');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('arbitrary text before a gif is NOT treated as a gif', () {
|
||||||
|
expect(GifHelper.parseGif('lol g:abc123'), isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a reply with plain text is not a gif', () {
|
||||||
|
expect(GifHelper.parseGif('@[Bob] hello there'), isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('media.giphy.com URL resolves', () {
|
||||||
|
expect(
|
||||||
|
GifHelper.parseGif('https://media.giphy.com/media/abc123/giphy.gif'),
|
||||||
|
'abc123',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reply + gif URL resolves', () {
|
||||||
|
expect(
|
||||||
|
GifHelper.parseGif(
|
||||||
|
'@[Bob] https://media.giphy.com/media/abc123/giphy.gif',
|
||||||
|
),
|
||||||
|
'abc123',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('plain text is null', () {
|
||||||
|
expect(GifHelper.parseGif('hello world'), isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:meshcore_open/widgets/translated_message_content.dart';
|
||||||
|
|
||||||
|
Future<void> _pump(WidgetTester tester, String text) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: TranslatedMessageContent(
|
||||||
|
displayText: text,
|
||||||
|
style: const TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('TranslatedMessageContent mention rendering (#233)', () {
|
||||||
|
testWidgets('inline mention renders as a chip, not literal', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await _pump(tester, 'hey @[Bob] how are you');
|
||||||
|
expect(find.text('@Bob'), findsOneWidget); // chip
|
||||||
|
expect(find.textContaining('@[Bob]'), findsNothing); // no literal
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('leading mention renders as a chip', (tester) async {
|
||||||
|
await _pump(tester, '@[Bob] hello');
|
||||||
|
expect(find.text('@Bob'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('multiple mentions each render as chips', (tester) async {
|
||||||
|
await _pump(tester, '@[Alice] and @[Bob]');
|
||||||
|
expect(find.text('@Alice'), findsOneWidget);
|
||||||
|
expect(find.text('@Bob'), findsOneWidget);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in new issue