@@ -301,6 +301,63 @@ final class FlowCryptCoreTests: XCTestCase {
301301 XCTAssertNotNil ( b. content. range ( of: text) ) // original text contained within the formatted html block
302302 }
303303
304+ // Regression test for https://github.com/FlowCrypt/flowcrypt-ios/issues/630
305+ // Verifies that emoji / non-BMP Unicode scalars survive the full
306+ // compose -> encrypt -> parseDecryptMsg round-trip through the JS core
307+ // (WKWebView bridge). If this passes, any user-visible emoji rendering
308+ // issue is located above the Core layer (i.e. in ThreadDetailWebNode /
309+ // WKWebView HTML rendering).
310+ func testEndToEndWithEmoji( ) async throws {
311+ let passphrase = " some pass phrase test "
312+ let email = " e2e-emoji@domain.com "
313+ // Mix BMP emoji, supplementary-plane emoji (surrogate pair in UTF-16),
314+ // ZWJ sequence, combining mark, and Chinese character.
315+ let text = " Hello 😀 🙂 🔐 👩💻 é 汉 "
316+ let generateKeyRes = try await core. generateKey (
317+ passphrase: passphrase,
318+ variant: KeyVariant . curve25519,
319+ userIds: [ UserId ( email: email, name: " End to end emoji " ) ]
320+ )
321+ let msg = SendableMsg (
322+ text: text,
323+ html: text,
324+ to: [ email] ,
325+ cc: [ ] ,
326+ bcc: [ ] ,
327+ from: email,
328+ subject: " emoji subj 😀 " ,
329+ replyToMsgId: nil ,
330+ inReplyTo: nil ,
331+ atts: [ ] ,
332+ pubKeys: [ generateKeyRes. key. public] ,
333+ signingPrv: nil ,
334+ password: nil
335+ )
336+ let mime = try await core. composeEmail ( msg: msg, fmt: . encryptInline)
337+ let keys = try [ Keypair ( generateKeyRes. key, passPhrase: passphrase, source: " test " ) ]
338+ let decrypted = try await core. parseDecryptMsg (
339+ encrypted: mime. mimeEncoded,
340+ keys: keys,
341+ msgPwd: nil ,
342+ isMime: true ,
343+ verificationPubKeys: [ ]
344+ )
345+ XCTAssertEqual ( decrypted. replyType, ReplyType . encrypted)
346+ // The plain text field must match byte-for-byte after round-trip.
347+ XCTAssertEqual ( decrypted. text, text)
348+ XCTAssertEqual ( decrypted. blocks. count, 1 )
349+ let b = decrypted. blocks [ 0 ]
350+ XCTAssertEqual ( b. type, MsgBlock . BlockType. plainHtml)
351+ XCTAssertNil ( b. decryptErr)
352+ // Each emoji/unicode scalar must appear intact inside the rendered html block.
353+ for scalar in [ " 😀 " , " 🙂 " , " 🔐 " , " 👩💻 " , " é " , " 汉 " ] {
354+ XCTAssertNotNil (
355+ b. content. range ( of: scalar) ,
356+ " expected \( scalar) to survive round-trip inside rendered html block "
357+ )
358+ }
359+ }
360+
304361 func testDecryptErrMismatch( ) async throws {
305362 let key = TestData . k0
306363 let r = try await core. parseDecryptMsg ( encrypted: TestData . mismatchEncryptedMsg. data ( using: . utf8) !, keys: [ key] , msgPwd: nil , isMime: false , verificationPubKeys: [ ] )
0 commit comments