use js_int::{UInt, uint};
use ruma_macros::EventContent;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
#[ruma_event(type = "m.recent_emoji", kind = GlobalAccountData)]
pub struct RecentEmojiEventContent {
#[serde(default, deserialize_with = "ruma_common::serde::ignore_invalid_vec_items")]
pub recent_emoji: Vec<RecentEmoji>,
}
impl RecentEmojiEventContent {
pub const RECOMMENDED_MAX_LEN: usize = 100;
pub fn new(recent_emoji: Vec<RecentEmoji>) -> Self {
Self { recent_emoji }
}
pub fn increment_emoji_total(&mut self, emoji: &str) {
self.recent_emoji.truncate(Self::RECOMMENDED_MAX_LEN);
if let Some(position) = self.recent_emoji.iter().position(|e| e.emoji == emoji) {
let total = &mut self.recent_emoji[position].total;
*total = (*total).saturating_add(uint!(1));
if position > 0 {
let emoji = self.recent_emoji.remove(position);
self.recent_emoji.insert(0, emoji);
}
} else {
let emoji = RecentEmoji::new(emoji.to_owned());
self.recent_emoji.insert(0, emoji);
self.recent_emoji.truncate(Self::RECOMMENDED_MAX_LEN);
}
}
pub fn recent_emoji_sorted_by_total(&self) -> Vec<RecentEmoji> {
let mut recent_emoji =
self.recent_emoji.iter().take(Self::RECOMMENDED_MAX_LEN).cloned().collect::<Vec<_>>();
recent_emoji.sort_by_key(|emoji| std::cmp::Reverse(emoji.total));
recent_emoji
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct RecentEmoji {
pub emoji: String,
pub total: UInt,
}
impl RecentEmoji {
pub fn new(emoji: String) -> Self {
Self { emoji, total: uint!(1) }
}
}
#[cfg(test)]
mod tests {
use assert_matches2::assert_matches;
use js_int::uint;
use ruma_common::canonical_json::assert_to_canonical_json_eq;
use serde_json::{from_value as from_json_value, json};
use super::{RecentEmoji, RecentEmojiEventContent};
use crate::AnyGlobalAccountDataEvent;
#[test]
fn recent_emoji_serialization() {
let content = RecentEmojiEventContent::new([RecentEmoji::new("😎".to_owned())].into());
assert_to_canonical_json_eq!(
content,
json!({
"recent_emoji": [{
"emoji": "😎",
"total": 1,
}],
}),
);
}
#[test]
fn recent_emoji_deserialization() {
let json = json!({
"content": {
"recent_emoji": [
{
"emoji": "😎",
"total": 1,
},
{
"emoji": "🏠",
"total": -1,
},
],
},
"type": "m.recent_emoji",
});
assert_matches!(
from_json_value::<AnyGlobalAccountDataEvent>(json),
Ok(AnyGlobalAccountDataEvent::RecentEmoji(ev))
);
assert_eq!(ev.content.recent_emoji, [RecentEmoji::new("😎".to_owned())]);
}
#[test]
fn recent_emoji_increment() {
let json = json!({
"recent_emoji": [
{
"emoji": "😎",
"total": 1,
},
{
"emoji": "🏠",
"total": 5,
},
{
"emoji": "🧑💻",
"total": 2,
},
],
});
let mut content = from_json_value::<RecentEmojiEventContent>(json).unwrap();
let mut iter = content.recent_emoji.iter();
assert_eq!(iter.next().unwrap().emoji, "😎");
assert_eq!(iter.next().unwrap().emoji, "🏠");
assert_eq!(iter.next().unwrap().emoji, "🧑💻");
assert_eq!(iter.next(), None);
content.increment_emoji_total("🏠");
assert_eq!(content.recent_emoji.first().unwrap().total, uint!(6));
let mut iter = content.recent_emoji.iter();
assert_eq!(iter.next().unwrap().emoji, "🏠");
assert_eq!(iter.next().unwrap().emoji, "😎");
assert_eq!(iter.next().unwrap().emoji, "🧑💻");
assert_eq!(iter.next(), None);
content.increment_emoji_total("💩");
assert_eq!(content.recent_emoji.first().unwrap().total, uint!(1));
let mut iter = content.recent_emoji.iter();
assert_eq!(iter.next().unwrap().emoji, "💩");
assert_eq!(iter.next().unwrap().emoji, "🏠");
assert_eq!(iter.next().unwrap().emoji, "😎");
assert_eq!(iter.next().unwrap().emoji, "🧑💻");
assert_eq!(iter.next(), None);
let first_emoji = "\u{2700}";
let first_emoji_u32 = 0x2700_u32;
let mut content = RecentEmojiEventContent::new(
std::iter::repeat_n(first_emoji_u32, 110)
.enumerate()
.map(|(n, start)| {
let char = char::from_u32(start + (n as u32)).unwrap();
RecentEmoji::new(char.into())
})
.collect(),
);
assert_eq!(content.recent_emoji.len(), 110);
content.increment_emoji_total(first_emoji);
assert_eq!(content.recent_emoji.first().unwrap().total, uint!(2));
assert_eq!(content.recent_emoji.len(), 100);
}
#[test]
fn recent_emoji_sorted_by_total() {
let json = json!({
"recent_emoji": [
{
"emoji": "😎",
"total": 1,
},
{
"emoji": "🏠",
"total": 5,
},
{
"emoji": "🧑💻",
"total": 2,
},
{
"emoji": "🚀",
"total": 1,
},
],
});
let content = from_json_value::<RecentEmojiEventContent>(json).unwrap();
let mut iter = content.recent_emoji.iter();
assert_eq!(iter.next().unwrap().emoji, "😎");
assert_eq!(iter.next().unwrap().emoji, "🏠");
assert_eq!(iter.next().unwrap().emoji, "🧑💻");
assert_eq!(iter.next().unwrap().emoji, "🚀");
assert_eq!(iter.next(), None);
let sorted = content.recent_emoji_sorted_by_total();
let mut iter = sorted.iter();
assert_eq!(iter.next().unwrap().emoji, "🏠");
assert_eq!(iter.next().unwrap().emoji, "🧑💻");
assert_eq!(iter.next().unwrap().emoji, "😎");
assert_eq!(iter.next().unwrap().emoji, "🚀");
assert_eq!(iter.next(), None);
}
}