From f6681b47aa4fbc329f3ba85295ef378b1342724a Mon Sep 17 00:00:00 2001 From: Strycher Date: Sun, 19 Jul 2026 03:34:43 -0400 Subject: [PATCH] fix(#284): summarise GIF notifications via GifHelper, not a duplicate regex Notifications summarised a GIF as "Sent a GIF" using their own hand-rolled `^g:[A-Za-z0-9_-]+$` regex rather than GifHelper. #282 changed the payload to `https://giphy.com/gifs/`, which that regex does not match, so GIF notifications regressed to dumping the raw URL into the tray. Detection now goes through `GifHelper.parseGif`, matching how the adjacent reaction case already delegates to ReactionHelper. That restores the summary and covers every form the app can send, including replies (`@[Name] ` + payload), which the old regex never matched even for legacy `g:`. Verified no other hand-rolled `^g:`/`^m:`/`^s:`/`^r:` payload regexes remain outside their owning helpers, so this class of drift is closed. Co-Authored-By: Claude Opus 4.8 --- lib/services/notification_service.dart | 3 +- test/services/notification_text_test.dart | 67 +++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 test/services/notification_text_test.dart diff --git a/lib/services/notification_service.dart b/lib/services/notification_service.dart index 6e41745..d526b81 100644 --- a/lib/services/notification_service.dart +++ b/lib/services/notification_service.dart @@ -4,6 +4,7 @@ import 'dart:ui'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter/foundation.dart'; +import '../helpers/gif_helper.dart'; import '../helpers/reaction_helper.dart'; import '../l10n/app_localizations.dart'; import '../utils/platform_info.dart'; @@ -154,7 +155,7 @@ class NotificationService { if (reaction != null) { return 'Reacted ${reaction.emoji}'; } - if (RegExp(r'^g:[A-Za-z0-9_-]+$').hasMatch(trimmed)) { + if (GifHelper.parseGif(trimmed) != null) { return 'Sent a GIF'; } return text; diff --git a/test/services/notification_text_test.dart b/test/services/notification_text_test.dart new file mode 100644 index 0000000..8875798 --- /dev/null +++ b/test/services/notification_text_test.dart @@ -0,0 +1,67 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:meshcore_open/helpers/gif_helper.dart'; +import 'package:meshcore_open/services/notification_service.dart'; + +// #284: notifications summarised a GIF as "Sent a GIF" using their own +// hand-rolled `^g:$` regex. #282 changed the payload to a Giphy URL, which +// that regex does not match, so notifications regressed to dumping the raw URL. +// Detection must go through GifHelper.parseGif so every payload form the app +// can actually send is summarised, and so the regex cannot drift again. +void main() { + const gifId = 'zaMiq1BvCdAVIiu3Mb'; + + group('GIF payloads are summarised, not dumped raw', () { + test('the current URL payload summarises', () { + expect( + NotificationService.formatNotificationText(GifHelper.encodeGif(gifId)), + 'Sent a GIF', + ); + }); + + test('a reply carrying a GIF summarises', () { + expect( + NotificationService.formatNotificationText( + '@[Some Node] ${GifHelper.encodeGif(gifId)}', + ), + 'Sent a GIF', + ); + }); + + test('a legacy g: payload still summarises', () { + expect( + NotificationService.formatNotificationText('g:$gifId'), + 'Sent a GIF', + ); + }); + + test('a legacy g: reply still summarises', () { + expect( + NotificationService.formatNotificationText('@[Some Node] g:$gifId'), + 'Sent a GIF', + ); + }); + }); + + group('everything else is untouched', () { + test('a reaction still summarises', () { + expect( + NotificationService.formatNotificationText('r:abcd:00'), + startsWith('Reacted '), + ); + }); + + test('ordinary text passes through unchanged', () { + expect( + NotificationService.formatNotificationText('hey are you there'), + 'hey are you there', + ); + }); + + test('a non-GIF URL is not mistaken for a GIF', () { + expect( + NotificationService.formatNotificationText('https://example.com/page'), + 'https://example.com/page', + ); + }); + }); +}