use std::borrow::Cow;
use assert_matches2::assert_matches;
use js_int::uint;
use ruma_common::{
mxc_uri, owned_event_id, owned_room_id, owned_user_id,
serde::{Base64, Raw},
user_id, MilliSecondsSinceUnixEpoch, OwnedDeviceId,
};
use ruma_events::{
key::verification::VerificationMethod,
room::{
message::{
AddMentions, AudioMessageEventContent, EmoteMessageEventContent,
FileMessageEventContent, FormattedBody, ForwardThread, ImageMessageEventContent,
KeyVerificationRequestEventContent, MessageType, OriginalRoomMessageEvent,
OriginalSyncRoomMessageEvent, Relation, ReplyWithinThread, RoomMessageEventContent,
TextMessageEventContent, VideoMessageEventContent,
},
EncryptedFileInit, JsonWebKeyInit, MediaSource,
},
EventContent, Mentions, MessageLikeUnsigned, RawExt,
};
use serde_json::{
from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue,
};
macro_rules! json_object {
( $($tt:tt)+ ) => {
match serde_json::json!({ $($tt)+ }) {
serde_json::value::Value::Object(map) => map,
_ => panic!("Not a JSON object"),
}
}
}
#[test]
fn custom_msgtype_serialization() {
let json_data = json_object! {
"custom_field": "baba",
"another_one": "abab",
};
let custom_msgtype =
MessageType::new("my_custom_msgtype", "my message body".into(), json_data).unwrap();
assert_eq!(
to_json_value(&custom_msgtype).unwrap(),
json!({
"msgtype": "my_custom_msgtype",
"body": "my message body",
"custom_field": "baba",
"another_one": "abab",
})
);
}
#[test]
fn custom_msgtype_deserialization() {
let json_data = json!({
"msgtype": "my_custom_msgtype",
"body": "my custom message",
"custom_field": "baba",
"another_one": "abab",
});
let expected_json_data = json_object! {
"custom_field": "baba",
"another_one": "abab",
};
let custom_event: MessageType = from_json_value(json_data).unwrap();
assert_eq!(custom_event.msgtype(), "my_custom_msgtype");
assert_eq!(custom_event.body(), "my custom message");
assert_eq!(custom_event.data(), Cow::Owned(expected_json_data));
}
#[test]
fn text_msgtype_formatted_body_serialization() {
let message_event_content =
RoomMessageEventContent::text_html("Hello, World!", "Hello, World!");
assert_eq!(
to_json_value(&message_event_content).unwrap(),
json!({
"body": "Hello, World!",
"msgtype": "m.text",
"format": "org.matrix.custom.html",
"formatted_body": "Hello, World!",
})
);
}
#[test]
fn text_msgtype_plain_text_serialization() {
let message_event_content =
RoomMessageEventContent::text_plain("> <@test:example.com> test\n\ntest reply");
assert_eq!(
to_json_value(&message_event_content).unwrap(),
json!({
"body": "> <@test:example.com> test\n\ntest reply",
"msgtype": "m.text"
})
);
}
#[test]
#[cfg(feature = "markdown")]
fn text_msgtype_markdown_serialization() {
use ruma_events::room::message::TextMessageEventContent;
let text = "Testing **bold** and _italic_!";
let formatted_message =
RoomMessageEventContent::new(MessageType::Text(TextMessageEventContent::markdown(text)));
assert_eq!(
to_json_value(&formatted_message).unwrap(),
json!({
"body": text,
"formatted_body": "Testing bold and italic!",
"format": "org.matrix.custom.html",
"msgtype": "m.text"
})
);
let text = "Testing a simple phraseβ¦";
let plain_message_simple =
RoomMessageEventContent::new(MessageType::Text(TextMessageEventContent::markdown(text)));
assert_eq!(
to_json_value(&plain_message_simple).unwrap(),
json!({
"body": text,
"msgtype": "m.text"
})
);
let text = "Testing\n\nSeveral\n\nParagraphs.";
let plain_message_paragraphs =
RoomMessageEventContent::new(MessageType::Text(TextMessageEventContent::markdown(text)));
assert_eq!(
to_json_value(&plain_message_paragraphs).unwrap(),
json!({
"body": text,
"formatted_body": "
Testing
\nSeveral
\nParagraphs.
\n",
"format": "org.matrix.custom.html",
"msgtype": "m.text"
})
);
let text = r#"Testing
A paragraph
with
a soft line break
* item 1
* item 2
item 2 (cont'd)
* item 3
```
line 1
line 2
```"#;
let plain_message_paragraphs =
RoomMessageEventContent::new(MessageType::Text(TextMessageEventContent::markdown(text)));
assert_eq!(
to_json_value(&plain_message_paragraphs).unwrap(),
json!({
"body": text,
"formatted_body": r#"Testing
A paragraph
with
a soft line break
- item 1
- item 2
item 2 (cont'd)
- item 3
line 1
line 2
"#,
"format": "org.matrix.custom.html",
"msgtype": "m.text"
})
);
}
#[test]
#[cfg(feature = "markdown")]
fn markdown_detection() {
use ruma_events::room::message::FormattedBody;
// No markdown
let formatted_body = FormattedBody::markdown("A simple message.");
assert_matches!(formatted_body, None);
// Multiple paragraphs trigger markdown
let formatted_body =
FormattedBody::markdown("A message\nwith\n\nmultiple\n\nparagraphs").unwrap();
assert_eq!(
formatted_body.body,
"A message
\nwith
\nmultiple
\nparagraphs
\n"
);
// HTML reserved symbols do not trigger markdown.
let formatted_body = FormattedBody::markdown("A message with & HTML < entities");
assert_matches!(formatted_body, None);
// HTML triggers markdown.
let formatted_body = FormattedBody::markdown("An HTML message").unwrap();
assert_eq!(formatted_body.body, "An HTML message");
}
#[test]
#[cfg(feature = "markdown")]
fn markdown_options() {
use ruma_events::room::message::FormattedBody;
// Tables
let formatted_body = FormattedBody::markdown(
"|head1|head2|\n\
|---|---|\n\
|body1|body2|\
",
);
assert_eq!(
formatted_body.unwrap().body,
"\
head1 | head2 |
\
\nbody1 | body2 |
\n\
\n"
);
// Strikethrough
let formatted_body = FormattedBody::markdown("A message with a ~~strike~~");
assert_eq!(formatted_body.unwrap().body, "A message with a strike");
}
#[test]
fn verification_request_msgtype_deserialization() {
let user_id = user_id!("@example2:localhost");
let device_id: OwnedDeviceId = "XOWLHHFSWM".into();
let json_data = json!({
"body": "@example:localhost is requesting to verify your key, ...",
"msgtype": "m.key.verification.request",
"to": user_id,
"from_device": device_id,
"methods": [
"m.sas.v1",
"m.qr_code.show.v1",
"m.reciprocate.v1"
]
});
let content = from_json_value::(json_data).unwrap();
assert_matches!(content.msgtype, MessageType::VerificationRequest(verification));
assert_eq!(verification.body, "@example:localhost is requesting to verify your key, ...");
assert_eq!(verification.to, user_id);
assert_eq!(verification.from_device, device_id);
assert_eq!(verification.methods.len(), 3);
assert!(verification.methods.contains(&VerificationMethod::SasV1));
assert_matches!(content.relates_to, None);
}
#[test]
fn verification_request_msgtype_serialization() {
let user_id = owned_user_id!("@example2:localhost");
let device_id: OwnedDeviceId = "XOWLHHFSWM".into();
let body = "@example:localhost is requesting to verify your key, ...".to_owned();
let methods =
vec![VerificationMethod::SasV1, "m.qr_code.show.v1".into(), "m.reciprocate.v1".into()];
let json_data = json!({
"body": body,
"msgtype": "m.key.verification.request",
"to": user_id,
"from_device": device_id,
"methods": methods
});
let content = MessageType::VerificationRequest(KeyVerificationRequestEventContent::new(
body, methods, device_id, user_id,
));
assert_eq!(to_json_value(&content).unwrap(), json_data,);
}
#[test]
fn content_deserialization_failure() {
let json_data = json!({
"body": "test",
"msgtype": "m.location",
"url": "http://example.com/audio.mp3"
});
assert_matches!(from_json_value::(json_data), Err(_));
}
#[test]
fn reply_thread_fallback() {
let thread_root = OriginalRoomMessageEvent {
content: RoomMessageEventContent::text_plain("Thread root"),
event_id: owned_event_id!("$thread_root"),
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)),
room_id: owned_room_id!("!testroomid:example.org"),
sender: owned_user_id!("@user:example.org"),
unsigned: MessageLikeUnsigned::default(),
};
let threaded_message = OriginalRoomMessageEvent {
content: RoomMessageEventContent::text_plain("Threaded message").make_for_thread(
&thread_root,
ReplyWithinThread::No,
AddMentions::No,
),
event_id: owned_event_id!("$threaded_message"),
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)),
room_id: owned_room_id!("!testroomid:example.org"),
sender: owned_user_id!("@user:example.org"),
unsigned: MessageLikeUnsigned::default(),
};
let reply_as_thread_fallback = RoomMessageEventContent::text_plain(
"Reply from a thread-incapable client",
)
.make_reply_to(&threaded_message, ForwardThread::Yes, AddMentions::No);
let relation = reply_as_thread_fallback.relates_to.unwrap();
assert_matches!(relation, Relation::Thread(thread_info));
assert_eq!(
thread_info.in_reply_to.map(|in_reply_to| in_reply_to.event_id),
Some(threaded_message.event_id)
);
assert_eq!(thread_info.event_id, thread_root.event_id);
assert!(thread_info.is_falling_back);
}
#[test]
fn reply_thread_serialization_roundtrip() {
let thread_root = OriginalRoomMessageEvent {
content: RoomMessageEventContent::text_plain("Thread root"),
event_id: owned_event_id!("$thread_root"),
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)),
room_id: owned_room_id!("!testroomid:example.org"),
sender: owned_user_id!("@user:example.org"),
unsigned: MessageLikeUnsigned::default(),
};
let threaded_message = OriginalRoomMessageEvent {
content: RoomMessageEventContent::text_plain("Threaded message").make_for_thread(
&thread_root,
ReplyWithinThread::No,
AddMentions::No,
),
event_id: owned_event_id!("$threaded_message"),
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)),
room_id: owned_room_id!("!testroomid:example.org"),
sender: owned_user_id!("@user:example.org"),
unsigned: MessageLikeUnsigned::default(),
};
let reply_as_thread_fallback = RoomMessageEventContent::text_plain(
"Reply from a thread client",
)
.make_reply_to(&threaded_message, ForwardThread::Yes, AddMentions::No);
let as_raw = Raw::new(&reply_as_thread_fallback).unwrap();
let reply_as_thread_fallback =
as_raw.deserialize_with_type(reply_as_thread_fallback.event_type()).unwrap();
let relation = reply_as_thread_fallback.relates_to.unwrap();
assert_matches!(relation, Relation::Thread(thread_info));
assert_eq!(
thread_info.in_reply_to.map(|in_reply_to| in_reply_to.event_id),
Some(threaded_message.event_id)
);
assert_eq!(thread_info.event_id, thread_root.event_id);
assert!(thread_info.is_falling_back);
}
#[test]
fn reply_add_mentions() {
let user = owned_user_id!("@user:example.org");
let friend = owned_user_id!("@friend:example.org");
let other_friend = owned_user_id!("@other_friend:example.org");
let mut first_message_content = RoomMessageEventContent::text_plain("My friend!");
first_message_content.mentions = Some(Mentions::with_user_ids([friend.clone()]));
let first_message = OriginalRoomMessageEvent {
content: first_message_content,
event_id: owned_event_id!("$143273582443PhrSn"),
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)),
room_id: owned_room_id!("!testroomid:example.org"),
sender: user.clone(),
unsigned: MessageLikeUnsigned::default(),
};
let mut second_message = RoomMessageEventContent::text_plain("User! Other friend!")
.make_reply_to(&first_message, ForwardThread::Yes, AddMentions::Yes);
let mentions = second_message.mentions.clone().unwrap();
assert_eq!(mentions.user_ids, [user.clone()].into());
assert!(!mentions.room);
second_message =
second_message.add_mentions(Mentions::with_user_ids([user.clone(), other_friend.clone()]));
let mentions = second_message.mentions.clone().unwrap();
assert_eq!(mentions.user_ids, [other_friend.clone(), user.clone()].into());
assert!(!mentions.room);
second_message = second_message.add_mentions(Mentions::with_room_mention());
let mentions = second_message.mentions.unwrap();
assert_eq!(mentions.user_ids, [other_friend, user].into());
assert!(mentions.room);
}
#[test]
fn make_replacement() {
let content = RoomMessageEventContent::text_html(
"This is _an edited_ message.",
"This is an edited message.",
);
let original_message_json = json!({
"content": {
"body": "Hello, World!",
"msgtype": "m.text",
},
"event_id": "$143273582443PhrSn",
"origin_server_ts": 134_829_848,
"room_id": "!roomid:notareal.hs",
"sender": "@user:notareal.hs",
"type": "m.room.message",
});
let original_message: OriginalSyncRoomMessageEvent =
from_json_value(original_message_json).unwrap();
let content = content.make_replacement(&original_message);
assert_matches!(
content.msgtype,
MessageType::Text(TextMessageEventContent { body, formatted, .. })
);
assert_eq!(body, "* This is _an edited_ message.");
let formatted = formatted.unwrap();
assert_eq!(formatted.body, "* This is an edited message.");
assert_matches!(content.mentions, None);
}
#[test]
fn audio_msgtype_serialization() {
let message_event_content =
RoomMessageEventContent::new(MessageType::Audio(AudioMessageEventContent::plain(
"Upload: my_song.mp3".to_owned(),
mxc_uri!("mxc://notareal.hs/file").to_owned(),
)));
assert_eq!(
to_json_value(&message_event_content).unwrap(),
json!({
"body": "Upload: my_song.mp3",
"url": "mxc://notareal.hs/file",
"msgtype": "m.audio",
})
);
}
#[test]
fn audio_msgtype_deserialization() {
let json_data = json!({
"body": "Upload: my_song.mp3",
"url": "mxc://notareal.hs/file",
"msgtype": "m.audio",
});
let event_content = from_json_value::(json_data).unwrap();
assert_matches!(event_content.msgtype, MessageType::Audio(content));
assert_eq!(content.body, "Upload: my_song.mp3");
assert_matches!(&content.source, MediaSource::Plain(url));
assert_eq!(url, "mxc://notareal.hs/file");
assert!(content.caption().is_none());
}
#[test]
fn file_msgtype_plain_content_serialization() {
let message_event_content =
RoomMessageEventContent::new(MessageType::File(FileMessageEventContent::plain(
"Upload: my_file.txt".to_owned(),
mxc_uri!("mxc://notareal.hs/file").to_owned(),
)));
assert_eq!(
to_json_value(&message_event_content).unwrap(),
json!({
"body": "Upload: my_file.txt",
"url": "mxc://notareal.hs/file",
"msgtype": "m.file",
})
);
}
#[test]
fn file_msgtype_encrypted_content_serialization() {
let message_event_content =
RoomMessageEventContent::new(MessageType::File(FileMessageEventContent::encrypted(
"Upload: my_file.txt".to_owned(),
EncryptedFileInit {
url: mxc_uri!("mxc://notareal.hs/file").to_owned(),
key: JsonWebKeyInit {
kty: "oct".to_owned(),
key_ops: vec!["encrypt".to_owned(), "decrypt".to_owned()],
alg: "A256CTR".to_owned(),
k: Base64::parse("TLlG_OpX807zzQuuwv4QZGJ21_u7weemFGYJFszMn9A").unwrap(),
ext: true,
}
.into(),
iv: Base64::parse("S22dq3NAX8wAAAAAAAAAAA").unwrap(),
hashes: [(
"sha256".to_owned(),
Base64::parse("aWOHudBnDkJ9IwaR1Nd8XKoI7DOrqDTwt6xDPfVGN6Q").unwrap(),
)]
.into(),
v: "v2".to_owned(),
}
.into(),
)));
assert_eq!(
to_json_value(&message_event_content).unwrap(),
json!({
"body": "Upload: my_file.txt",
"file": {
"url": "mxc://notareal.hs/file",
"key": {
"kty": "oct",
"key_ops": ["encrypt", "decrypt"],
"alg": "A256CTR",
"k": "TLlG_OpX807zzQuuwv4QZGJ21_u7weemFGYJFszMn9A",
"ext": true
},
"iv": "S22dq3NAX8wAAAAAAAAAAA",
"hashes": {
"sha256": "aWOHudBnDkJ9IwaR1Nd8XKoI7DOrqDTwt6xDPfVGN6Q"
},
"v": "v2",
},
"msgtype": "m.file",
})
);
}
#[test]
fn file_msgtype_plain_content_deserialization() {
let json_data = json!({
"body": "Upload: my_file.txt",
"url": "mxc://notareal.hs/file",
"msgtype": "m.file",
});
let event_content = from_json_value::(json_data).unwrap();
assert_matches!(event_content.msgtype, MessageType::File(content));
assert_eq!(content.body, "Upload: my_file.txt");
assert_matches!(&content.source, MediaSource::Plain(url));
assert_eq!(url, "mxc://notareal.hs/file");
assert!(content.caption().is_none());
}
#[test]
fn file_msgtype_encrypted_content_deserialization() {
let json_data = json!({
"body": "Upload: my_file.txt",
"file": {
"url": "mxc://notareal.hs/file",
"key": {
"kty": "oct",
"key_ops": ["encrypt", "decrypt"],
"alg": "A256CTR",
"k": "TLlG_OpX807zzQuuwv4QZGJ21_u7weemFGYJFszMn9A",
"ext": true
},
"iv": "S22dq3NAX8wAAAAAAAAAAA",
"hashes": {
"sha256": "aWOHudBnDkJ9IwaR1Nd8XKoI7DOrqDTwt6xDPfVGN6Q"
},
"v": "v2",
},
"msgtype": "m.file",
});
let event_content = from_json_value::(json_data).unwrap();
assert_matches!(event_content.msgtype, MessageType::File(content));
assert_eq!(content.body, "Upload: my_file.txt");
assert_matches!(content.source, MediaSource::Encrypted(encrypted_file));
assert_eq!(encrypted_file.url, "mxc://notareal.hs/file");
}
#[test]
fn image_msgtype_serialization() {
let message_event_content =
RoomMessageEventContent::new(MessageType::Image(ImageMessageEventContent::plain(
"Upload: my_image.jpg".to_owned(),
mxc_uri!("mxc://notareal.hs/file").to_owned(),
)));
assert_eq!(
to_json_value(&message_event_content).unwrap(),
json!({
"body": "Upload: my_image.jpg",
"url": "mxc://notareal.hs/file",
"msgtype": "m.image",
})
);
}
#[test]
fn image_msgtype_deserialization() {
let json_data = json!({
"body": "Upload: my_image.jpg",
"url": "mxc://notareal.hs/file",
"msgtype": "m.image",
});
let event_content = from_json_value::(json_data).unwrap();
assert_matches!(event_content.msgtype, MessageType::Image(content));
assert_eq!(content.body, "Upload: my_image.jpg");
assert_matches!(&content.source, MediaSource::Plain(url));
assert_eq!(url, "mxc://notareal.hs/file");
assert!(content.caption().is_none());
}
#[cfg(not(feature = "unstable-msc3488"))]
#[test]
fn location_msgtype_serialization() {
use ruma_events::room::message::LocationMessageEventContent;
let message_event_content =
RoomMessageEventContent::new(MessageType::Location(LocationMessageEventContent::new(
"Alice was at geo:51.5008,0.1247;u=35".to_owned(),
"geo:51.5008,0.1247;u=35".to_owned(),
)));
assert_eq!(
to_json_value(&message_event_content).unwrap(),
json!({
"body": "Alice was at geo:51.5008,0.1247;u=35",
"geo_uri": "geo:51.5008,0.1247;u=35",
"msgtype": "m.location",
})
);
}
#[test]
fn location_msgtype_deserialization() {
let json_data = json!({
"body": "Alice was at geo:51.5008,0.1247;u=35",
"geo_uri": "geo:51.5008,0.1247;u=35",
"msgtype": "m.location",
});
let event_content = from_json_value::(json_data).unwrap();
assert_matches!(event_content.msgtype, MessageType::Location(content));
assert_eq!(content.body, "Alice was at geo:51.5008,0.1247;u=35");
assert_eq!(content.geo_uri, "geo:51.5008,0.1247;u=35");
}
#[test]
fn text_msgtype_body_deserialization() {
let json_data = json!({
"body": "test",
"msgtype": "m.text",
});
assert_matches!(
from_json_value::(json_data),
Ok(RoomMessageEventContent { msgtype: MessageType::Text(content), .. })
);
assert_eq!(content.body, "test");
}
#[test]
fn text_msgtype_formatted_body_and_body_deserialization() {
let json_data = json!({
"body": "test",
"formatted_body": "test
",
"format": "org.matrix.custom.html",
"msgtype": "m.text",
});
assert_matches!(
from_json_value::(json_data),
Ok(RoomMessageEventContent { msgtype: MessageType::Text(content), .. })
);
assert_eq!(content.body, "test");
let formatted = content.formatted.unwrap();
assert_eq!(formatted.body, "test
");
}
#[test]
fn notice_msgtype_serialization() {
let message_event_content =
RoomMessageEventContent::notice_plain("> <@test:example.com> test\n\ntest reply");
assert_eq!(
to_json_value(&message_event_content).unwrap(),
json!({
"body": "> <@test:example.com> test\n\ntest reply",
"msgtype": "m.notice",
})
);
}
#[test]
fn notice_msgtype_deserialization() {
let json_data = json!({
"body": "test",
"msgtype": "m.notice",
});
assert_matches!(
from_json_value::(json_data),
Ok(RoomMessageEventContent { msgtype: MessageType::Notice(content), .. })
);
assert_eq!(content.body, "test");
}
#[test]
fn emote_msgtype_serialization() {
let message_event_content = RoomMessageEventContent::new(MessageType::Emote(
EmoteMessageEventContent::plain("> <@test:example.com> test\n\ntest reply"),
));
assert_eq!(
to_json_value(&message_event_content).unwrap(),
json!({
"body": "> <@test:example.com> test\n\ntest reply",
"msgtype": "m.emote",
})
);
}
#[test]
fn emote_msgtype_deserialization() {
let json_data = json!({
"body": "test",
"msgtype": "m.emote",
});
assert_matches!(
from_json_value::(json_data),
Ok(RoomMessageEventContent { msgtype: MessageType::Emote(content), .. })
);
assert_eq!(content.body, "test");
}
#[test]
fn video_msgtype_serialization() {
let message_event_content =
RoomMessageEventContent::new(MessageType::Video(VideoMessageEventContent::plain(
"Upload: my_video.mp4".to_owned(),
mxc_uri!("mxc://notareal.hs/file").to_owned(),
)));
assert_eq!(
to_json_value(&message_event_content).unwrap(),
json!({
"body": "Upload: my_video.mp4",
"url": "mxc://notareal.hs/file",
"msgtype": "m.video",
})
);
}
#[test]
fn video_msgtype_deserialization() {
let json_data = json!({
"body": "Upload: my_video.mp4",
"url": "mxc://notareal.hs/file",
"msgtype": "m.video",
});
let event_content = from_json_value::(json_data).unwrap();
assert_matches!(event_content.msgtype, MessageType::Video(content));
assert_eq!(content.body, "Upload: my_video.mp4");
assert_matches!(&content.source, MediaSource::Plain(url));
assert_eq!(url, "mxc://notareal.hs/file");
assert!(content.caption().is_none());
}
#[test]
#[allow(deprecated)]
fn set_mentions() {
let mut content = RoomMessageEventContent::text_plain("you!");
let mentions = content.mentions.take();
assert_matches!(mentions, None);
let user_id = owned_user_id!("@you:localhost");
content = content.set_mentions(Mentions::with_user_ids(vec![user_id.clone()]));
let mentions = content.mentions.unwrap();
assert_eq!(mentions.user_ids, [user_id].into());
}
#[test]
fn add_mentions_then_make_replacement() {
let alice = owned_user_id!("@alice:localhost");
let bob = owned_user_id!("@bob:localhost");
let original_message_json = json!({
"content": {
"body": "Hello, World!",
"msgtype": "m.text",
"m.mentions": {
"user_ids": [alice],
}
},
"event_id": "$143273582443PhrSn",
"origin_server_ts": 134_829_848,
"room_id": "!roomid:notareal.hs",
"sender": "@user:notareal.hs",
"type": "m.room.message",
});
let original_message: OriginalSyncRoomMessageEvent =
from_json_value(original_message_json).unwrap();
let mut content = RoomMessageEventContent::text_html(
"This is _an edited_ message.",
"This is an edited message.",
);
content = content.add_mentions(Mentions::with_user_ids(vec![alice.clone(), bob.clone()]));
content = content.make_replacement(&original_message);
let mentions = content.mentions.unwrap();
assert_eq!(mentions.user_ids, [bob.clone()].into());
assert_matches!(content.relates_to, Some(Relation::Replacement(replacement)));
let mentions = replacement.new_content.mentions.unwrap();
assert_eq!(mentions.user_ids, [alice, bob].into());
}
#[test]
fn make_replacement_then_add_mentions() {
let alice = owned_user_id!("@alice:localhost");
let bob = owned_user_id!("@bob:localhost");
let original_message_json = json!({
"content": {
"body": "Hello, World!",
"msgtype": "m.text",
"m.mentions": {
"user_ids": [alice],
}
},
"event_id": "$143273582443PhrSn",
"origin_server_ts": 134_829_848,
"room_id": "!roomid:notareal.hs",
"sender": "@user:notareal.hs",
"type": "m.room.message",
});
let original_message: OriginalSyncRoomMessageEvent =
from_json_value(original_message_json).unwrap();
let mut content = RoomMessageEventContent::text_html(
"This is _an edited_ message.",
"This is an edited message.",
);
content = content.make_replacement(&original_message);
content = content.add_mentions(Mentions::with_user_ids(vec![alice.clone(), bob.clone()]));
let mentions = content.mentions.unwrap();
assert_eq!(mentions.user_ids, [alice, bob].into());
assert_matches!(content.relates_to, Some(Relation::Replacement(replacement)));
assert!(replacement.new_content.mentions.is_none());
}
#[test]
fn mentions_room_deserialization() {
let json_data = json!({
"body": "room!",
"msgtype": "m.text",
"m.mentions": {
"room": true,
},
});
let content = from_json_value::(json_data).unwrap();
assert_matches!(content.msgtype, MessageType::Text(text));
assert_eq!(text.body, "room!");
let mentions = content.mentions.unwrap();
assert!(mentions.room);
}
#[test]
fn invalid_replacement() {
// As generated by Element Web: https://github.com/vector-im/element-web/issues/26554
let relation = json!({
"rel_type": "m.replace",
"event_id": "~!kCCQTCfnABLKGGvQjo:matrix.org:m1699715385559.77",
});
let json_data = json!({
"msgtype": "m.text",
"body": " * edited text",
"m.new_content": {
"msgtype": "m.text",
"body": "edited text",
"m.mentions": {},
},
"m.mentions": {},
"m.relates_to": relation
});
let content = from_json_value::(json_data).unwrap();
let relates_to = content.relates_to.unwrap();
let data = relates_to.data();
assert_matches!(&data, Cow::Borrowed(_)); // data is stored in JSON form because it's invalid
assert_eq!(JsonValue::Object(data.into_owned()), relation);
}
#[test]
fn test_audio_filename() {
let mut content = AudioMessageEventContent::plain(
"my_sound.ogg".to_owned(),
mxc_uri!("mxc://notareal.hs/abcdef").to_owned(),
);
assert_eq!(content.filename(), "my_sound.ogg");
content.body = "This was a great podcast episode".to_owned();
content.filename = Some("sound.ogg".to_owned());
assert_eq!(content.filename(), "sound.ogg");
}
#[test]
fn test_audio_caption() {
let mut content = AudioMessageEventContent::plain(
"my_sound.ogg".to_owned(),
mxc_uri!("mxc://notareal.hs/abcdef").to_owned(),
);
assert!(content.caption().is_none());
assert!(content.formatted_caption().is_none());
content.filename = Some("my_sound.ogg".to_owned());
assert!(content.caption().is_none());
assert!(content.formatted_caption().is_none());
content.body = "This was a great podcast episode".to_owned();
assert_eq!(content.caption(), Some("This was a great podcast episode"));
assert!(content.formatted_caption().is_none());
content.formatted =
Some(FormattedBody::html("This was a great podcast episode".to_owned()));
assert_eq!(content.caption(), Some("This was a great podcast episode"));
assert_eq!(
content.formatted_caption().map(|f| f.body.clone()),
Some("This was a great podcast episode".to_owned())
);
}
#[test]
fn test_file_filename() {
let mut content = FileMessageEventContent::plain(
"my_file.txt".to_owned(),
mxc_uri!("mxc://notareal.hs/abcdef").to_owned(),
);
assert_eq!(content.filename(), "my_file.txt");
content.body = "Please check these notes".to_owned();
content.filename = Some("notes.txt".to_owned());
assert_eq!(content.filename(), "notes.txt");
}
#[test]
fn test_file_caption() {
let mut content = FileMessageEventContent::plain(
"my_file.txt".to_owned(),
mxc_uri!("mxc://notareal.hs/abcdef").to_owned(),
);
assert!(content.caption().is_none());
assert!(content.formatted_caption().is_none());
content.filename = Some("my_file.txt".to_owned());
assert!(content.caption().is_none());
assert!(content.formatted_caption().is_none());
content.body = "Please check these notes".to_owned();
assert_eq!(content.caption(), Some("Please check these notes"));
assert!(content.formatted_caption().is_none());
content.formatted =
Some(FormattedBody::html("Please check these notes".to_owned()));
assert_eq!(content.caption(), Some("Please check these notes"));
assert_eq!(
content.formatted_caption().map(|f| f.body.clone()),
Some("Please check these notes".to_owned())
);
}
#[test]
fn test_image_filename() {
let mut content = ImageMessageEventContent::plain(
"my_image.jpg".to_owned(),
mxc_uri!("mxc://notareal.hs/abcdef").to_owned(),
);
assert_eq!(content.filename(), "my_image.jpg");
content.body = "Check it out π".to_owned();
content.filename = Some("image.jpg".to_owned());
assert_eq!(content.filename(), "image.jpg");
}
#[test]
fn test_image_caption() {
let mut content = ImageMessageEventContent::plain(
"my_image.jpg".to_owned(),
mxc_uri!("mxc://notareal.hs/abcdef").to_owned(),
);
assert!(content.caption().is_none());
assert!(content.formatted_caption().is_none());
content.filename = Some("my_image.jpg".to_owned());
assert!(content.caption().is_none());
assert!(content.formatted_caption().is_none());
content.body = "Check it out π".to_owned();
assert_eq!(content.caption(), Some("Check it out π"));
assert!(content.formatted_caption().is_none());
content.formatted = Some(FormattedBody::html("Check it out π
".to_owned()));
assert_eq!(content.caption(), Some("Check it out π"));
assert_eq!(
content.formatted_caption().map(|f| f.body.clone()),
Some("Check it out π
".to_owned())
);
}
#[test]
fn test_video_filename() {
let mut content = VideoMessageEventContent::plain(
"my_video.mp4".to_owned(),
mxc_uri!("mxc://notareal.hs/abcdef").to_owned(),
);
assert_eq!(content.filename(), "my_video.mp4");
content.body = "You missed a great evening".to_owned();
content.filename = Some("video.mp4".to_owned());
assert_eq!(content.filename(), "video.mp4");
}
#[test]
fn test_video_caption() {
let mut content = VideoMessageEventContent::plain(
"my_video.mp4".to_owned(),
mxc_uri!("mxc://notareal.hs/abcdef").to_owned(),
);
assert!(content.caption().is_none());
assert!(content.formatted_caption().is_none());
content.filename = Some("my_video.mp4".to_owned());
assert!(content.caption().is_none());
assert!(content.formatted_caption().is_none());
content.body = "You missed a great evening".to_owned();
assert_eq!(content.caption(), Some("You missed a great evening"));
assert!(content.formatted_caption().is_none());
content.formatted =
Some(FormattedBody::html("You missed a great evening".to_owned()));
assert_eq!(content.caption(), Some("You missed a great evening"));
assert_eq!(
content.formatted_caption().map(|f| f.body.clone()),
Some("You missed a great evening".to_owned())
);
}