From a497a6601269f9bc16b31871262c3fce6c258e64 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 27 Sep 2024 16:32:08 +0200 Subject: [PATCH] chore: Add more (un)localized body tests Covers a few edge cases that still fail. Changes to the unlocalizedBody function shouldn't cause behavioural changes apart from fixing a few edge cases. --- lib/src/event.dart | 35 ++-- test/event_test.dart | 447 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 468 insertions(+), 14 deletions(-) diff --git a/lib/src/event.dart b/lib/src/event.dart index 5768f01d..4e9710d7 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -761,7 +761,10 @@ class Event extends MatrixEvent { /// Returns a localized String representation of this event. For a /// room list you may find [withSenderNamePrefix] useful. Set [hideReply] to /// crop all lines starting with '>'. With [plaintextBody] it'll use the - /// plaintextBody instead of the normal body. + /// plaintextBody instead of the normal body which in practice will convert + /// the html body to a plain text body before falling back to the body. In + /// either case this function won't return the html body without converting + /// it to plain text. /// [removeMarkdown] allow to remove the markdown formating from the event body. /// Usefull form message preview or notifications text. Future calcLocalizedBody(MatrixLocalizations i18n, @@ -850,37 +853,41 @@ class Event extends MatrixEvent { } /// Calculating the body of an event regardless of localization. - String calcUnlocalizedBody( - {bool hideReply = false, - bool hideEdit = false, - bool plaintextBody = false, - bool removeMarkdown = false}) { + String calcUnlocalizedBody({ + bool hideReply = false, + bool hideEdit = false, + bool plaintextBody = false, + bool removeMarkdown = false, + }) { if (redacted) { return 'Removed by ${senderFromMemoryOrFallback.displayName ?? senderId}'; } var body = plaintextBody ? this.plaintextBody : this.body; - // we need to know if the message is an html message to be able to determine - // if we need to strip the reply fallback. - var htmlMessage = content['format'] != 'org.matrix.custom.html'; + // Html messages will already have their reply fallback removed during the Html to Text conversion. + var mayHaveReplyFallback = + !plaintextBody || content['format'] != 'org.matrix.custom.html'; + // If we have an edit, we want to operate on the new content final newContent = content.tryGetMap('m.new_content'); if (hideEdit && relationshipType == RelationshipTypes.edit && newContent != null) { if (plaintextBody && newContent['format'] == 'org.matrix.custom.html') { - htmlMessage = true; - body = HtmlToText.convert( - newContent.tryGet('formatted_body') ?? formattedText); + final newBody = newContent.tryGet('formatted_body'); + if (newBody != null) { + mayHaveReplyFallback = false; + body = HtmlToText.convert(newBody); + } } else { - htmlMessage = false; + mayHaveReplyFallback = true; body = newContent.tryGet('body') ?? body; } } // Hide reply fallback // Be sure that the plaintextBody already stripped teh reply fallback, // if the message is formatted - if (hideReply && (!plaintextBody || htmlMessage)) { + if (hideReply && mayHaveReplyFallback) { body = body.replaceFirst( RegExp(r'^>( \*)? <[^>]+>[^\n\r]+\r?\n(> [^\n]*\r?\n)*\r?\n'), ''); } diff --git a/test/event_test.dart b/test/event_test.dart index 3351182c..748e3c6c 100644 --- a/test/event_test.dart +++ b/test/event_test.dart @@ -1229,6 +1229,453 @@ void main() { expect(event2.body, 'm.room.message'); }); + group('unlocalized body reply stripping', () { + int i = 0; + + void testUnlocalizedBody({ + required Object? body, + required Object? formattedBody, + required bool html, + Object? editBody, + Object? editFormattedBody, + bool editHtml = false, + bool isEdit = false, + required String expectation, + required bool plaintextBody, + }) { + i += 1; + test('$i', () { + final event = Event.fromJson({ + 'type': EventTypes.Message, + 'content': { + 'msgtype': 'm.text', + if (body != null) 'body': body, + if (formattedBody != null) 'formatted_body': formattedBody, + if (html) 'format': 'org.matrix.custom.html', + if (isEdit) ...{ + 'm.new_content': { + if (editBody != null) 'body': editBody, + if (editFormattedBody != null) + 'formatted_body': editFormattedBody, + if (editHtml) 'format': 'org.matrix.custom.html', + }, + 'm.relates_to': { + 'event_id': '\$source2', + 'rel_type': RelationshipTypes.edit, + }, + }, + }, + 'event_id': '\$source', + 'sender': '@alice:example.org', + }, room); + + expect( + event.calcUnlocalizedBody( + hideReply: true, hideEdit: true, plaintextBody: plaintextBody), + expectation, + reason: + 'event was ${event.toJson()} and plaintextBody ${plaintextBody ? "was" : "was not"} set', + ); + }); + } + + // everything where we expect the body to be returned + testUnlocalizedBody( + expectation: 'body', + plaintextBody: false, + body: 'body', + formattedBody: 'formatted body', + html: false, + isEdit: false, + editBody: null, + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + // not sure we actually want m.room.message here and not an empty string + expectation: 'm.room.message', + plaintextBody: false, + body: '', + formattedBody: 'formatted body', + html: false, + isEdit: false, + editBody: null, + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'm.room.message', + plaintextBody: false, + body: null, + formattedBody: 'formatted body', + html: false, + isEdit: false, + editBody: null, + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'm.room.message', + plaintextBody: false, + body: 5, + formattedBody: 'formatted body', + html: false, + isEdit: false, + editBody: null, + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'body', + plaintextBody: false, + body: 'body', + formattedBody: 'formatted body', + html: false, + isEdit: true, + editBody: null, + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'body', + plaintextBody: false, + body: 'body', + formattedBody: null, + html: true, + isEdit: false, + editBody: null, + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'body', + plaintextBody: true, + body: 'body', + formattedBody: 'formatted body', + html: false, + isEdit: false, + editBody: null, + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'body', + plaintextBody: true, + body: 'body', + formattedBody: null, + html: true, + isEdit: false, + editBody: null, + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'body', + plaintextBody: true, + body: 'body', + // do we actually expect this to then use the body? + formattedBody: '', + html: true, + isEdit: false, + editBody: null, + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'body', + plaintextBody: true, + body: 'body', + formattedBody: 5, + html: true, + isEdit: false, + editBody: null, + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'body', + plaintextBody: false, + body: 'body', + formattedBody: 'formatted body', + html: true, + isEdit: true, + editBody: null, + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'body', + plaintextBody: false, + body: 'body', + formattedBody: 'formatted body', + html: true, + isEdit: true, + editBody: null, + editFormattedBody: null, + editHtml: true, + ); + testUnlocalizedBody( + expectation: '**formatted body**', + plaintextBody: true, + body: 'body', + formattedBody: 'formatted body', + html: true, + isEdit: true, + editBody: null, + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + expectation: '**formatted body**', + plaintextBody: true, + body: 'body', + formattedBody: 'formatted body', + html: true, + isEdit: true, + editBody: null, + editFormattedBody: null, + editHtml: true, + ); + + // everything where we expect the formatted body to be returned + testUnlocalizedBody( + expectation: '**formatted body**', + plaintextBody: true, + body: 'body', + formattedBody: 'formatted body', + html: true, + isEdit: false, + editBody: null, + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + expectation: '**formatted body**', + plaintextBody: true, + body: 5, + formattedBody: 'formatted body', + html: true, + isEdit: false, + editBody: null, + editFormattedBody: null, + editHtml: false, + ); + + // everything where we expect the edit body to be returned + testUnlocalizedBody( + expectation: 'edit body', + plaintextBody: false, + body: 'body', + formattedBody: 'formatted body', + html: false, + isEdit: true, + editBody: 'edit body', + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'edit body', + plaintextBody: false, + body: 'body', + formattedBody: 'formatted body', + html: false, + isEdit: true, + editBody: 'edit body', + editFormattedBody: 'edit formatted body', + editHtml: true, + ); + testUnlocalizedBody( + expectation: 'edit body', + plaintextBody: false, + body: 'body', + formattedBody: 'formatted body', + html: true, + isEdit: true, + editBody: 'edit body', + editFormattedBody: 'edit formatted body', + editHtml: true, + ); + testUnlocalizedBody( + expectation: 'edit body', + plaintextBody: false, + body: 'body', + formattedBody: 'formatted body', + html: false, + isEdit: true, + editBody: 'edit body', + editFormattedBody: 'edit formatted body', + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'edit body', + plaintextBody: false, + body: 'body', + formattedBody: 'formatted body', + html: false, + isEdit: true, + editBody: 'edit body', + editFormattedBody: null, + editHtml: true, + ); + testUnlocalizedBody( + expectation: 'edit body', + plaintextBody: false, + body: 'body', + formattedBody: 'formatted body', + html: true, + isEdit: true, + editBody: 'edit body', + editFormattedBody: 5, + editHtml: true, + ); + testUnlocalizedBody( + expectation: 'edit body', + plaintextBody: false, + body: 'body', + formattedBody: 'formatted body', + html: true, + isEdit: true, + editBody: 'edit body', + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'edit body', + plaintextBody: false, + body: 'body', + formattedBody: 'formatted body', + html: true, + isEdit: true, + editBody: 'edit body', + editFormattedBody: 'edit formatted body', + editHtml: false, + ); + + testUnlocalizedBody( + expectation: 'edit body', + plaintextBody: true, + body: 'body', + formattedBody: 'formatted body', + html: false, + isEdit: true, + editBody: 'edit body', + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'edit body', + plaintextBody: true, + body: 'body', + formattedBody: 'formatted body', + html: false, + isEdit: true, + editBody: 'edit body', + editFormattedBody: 'edit formatted body', + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'edit body', + plaintextBody: true, + body: 'body', + formattedBody: 'formatted body', + html: false, + isEdit: true, + editBody: 'edit body', + editFormattedBody: null, + editHtml: true, + ); + testUnlocalizedBody( + expectation: 'edit body', + plaintextBody: true, + body: 'body', + formattedBody: 'formatted body', + html: true, + isEdit: true, + editBody: 'edit body', + editFormattedBody: null, + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'edit body', + plaintextBody: true, + body: 'body', + formattedBody: 'formatted body', + html: true, + isEdit: true, + editBody: 'edit body', + editFormattedBody: 'edit formatted body', + editHtml: false, + ); + testUnlocalizedBody( + expectation: 'edit body', + plaintextBody: true, + body: 'body', + formattedBody: 'formatted body', + html: true, + isEdit: true, + editBody: 'edit body', + editFormattedBody: null, + editHtml: true, + ); + testUnlocalizedBody( + expectation: 'edit body', + plaintextBody: true, + body: 'body', + formattedBody: 'formatted body', + html: true, + isEdit: true, + editBody: 'edit body', + editFormattedBody: null, + editHtml: false, + ); + + // everything where we expect the edit formatted body to be returned + testUnlocalizedBody( + expectation: '**edit formatted body**', + plaintextBody: true, + body: 'body', + formattedBody: 'formatted body', + html: true, + isEdit: true, + editBody: null, + editFormattedBody: 'edit formatted body', + editHtml: true, + ); + testUnlocalizedBody( + expectation: '**edit formatted body**', + plaintextBody: true, + body: 'body', + formattedBody: 'formatted body', + html: true, + isEdit: true, + editBody: 'edit body', + editFormattedBody: 'edit formatted body', + editHtml: true, + ); + testUnlocalizedBody( + expectation: '**edit formatted body**', + plaintextBody: true, + body: null, + formattedBody: 'formatted body', + html: true, + isEdit: true, + editBody: 'edit body', + editFormattedBody: 'edit formatted body', + editHtml: true, + ); + testUnlocalizedBody( + expectation: '**edit formatted body**', + plaintextBody: true, + body: 'body', + formattedBody: null, + html: true, + isEdit: true, + editBody: 'edit body', + editFormattedBody: 'edit formatted body', + editHtml: true, + ); + }); + test('getDisplayEvent', () { final room = Room(id: '!1234', client: client); var event = Event.fromJson({