use std::collections::hash_map::DefaultHasher;
use std::fmt::{self, Write as _};
use std::hash::{Hash as _, Hasher as _};
use crate::tui::state::DashboardState;
fn hash_dbg<T: fmt::Debug>(hasher: &mut DefaultHasher, value: &T) {
struct DebugSink<'a>(&'a mut DefaultHasher);
impl fmt::Write for DebugSink<'_> {
fn write_str(&mut self, value: &str) -> fmt::Result {
self.0.write(value.as_bytes());
Ok(())
}
}
write!(DebugSink(hasher), "{value:?}").expect("writing into view hasher cannot fail");
}
pub(super) fn view_signature(state: &DashboardState) -> u64 {
let mut hasher = DefaultHasher::new();
hash_dbg(&mut hasher, &state.message_pane_source());
hash_dbg(&mut hasher, &state.selected_guild_id());
hash_dbg(&mut hasher, &state.selected_channel_id());
hash_dbg(&mut hasher, &state.active_modal_popup_kind());
hash_dbg(&mut hasher, &state.current_user());
hash_dbg(&mut hasher, &state.current_voice_self_status());
hash_dbg(&mut hasher, &state.update_available_version());
hash_dbg(&mut hasher, &state.visible_messages());
hash_dbg(&mut hasher, &state.visible_thread_card_items());
hash_dbg(&mut hasher, &state.typing_footer_for_selected_channel());
hash_dbg(&mut hasher, &state.composer_lock());
state.new_messages_count().hash(&mut hasher);
state.direct_message_unread_count().hash(&mut hasher);
for entry in state.visible_guild_pane_entries() {
hash_dbg(&mut hasher, &entry);
if let Some(guild) = entry.guild_state() {
hash_dbg(&mut hasher, &state.sidebar_guild_unread(guild.id));
}
}
for entry in state.visible_channel_pane_entries() {
hash_dbg(&mut hasher, &entry);
if let Some(channel) = entry.channel_state() {
hash_dbg(&mut hasher, &state.channel_unread(channel.id));
state
.channel_unread_message_count(channel.id)
.hash(&mut hasher);
}
}
let member_start = state.member_scroll();
let member_take = state.member_content_height();
for entry in state
.flattened_members()
.into_iter()
.skip(member_start)
.take(member_take)
{
hash_dbg(
&mut hasher,
&(
entry.user_id(),
entry.display_name(),
entry.username(),
entry.is_bot(),
entry.status(),
),
);
}
hash_dbg(&mut hasher, &state.selected_attachment_viewer_item());
hash_dbg(&mut hasher, &state.user_profile_popup_data());
hash_dbg(&mut hasher, &state.user_profile_popup_status());
hash_dbg(&mut hasher, &state.user_profile_popup_load_error());
hash_dbg(&mut hasher, &state.user_profile_popup_avatar_url());
hash_dbg(&mut hasher, &state.user_profile_popup_activities());
hash_dbg(&mut hasher, &state.user_profile_activity_picker_rows());
hash_dbg(&mut hasher, &state.attachment_downloads());
hash_dbg(&mut hasher, &state.reaction_users_popup());
hash_dbg(&mut hasher, &state.existing_emoji_reactions());
hash_dbg(&mut hasher, &state.own_emoji_reactions());
hash_dbg(&mut hasher, &state.filtered_emoji_reaction_items());
hash_dbg(&mut hasher, &state.poll_vote_picker_items());
hash_dbg(&mut hasher, &state.composer_mention_candidates());
hash_dbg(&mut hasher, &state.composer_emoji_candidates());
hash_dbg(&mut hasher, &state.composer_command_candidates());
hash_dbg(&mut hasher, &state.notification_inbox_items());
hash_dbg(&mut hasher, &state.notification_inbox_mentions_status());
state.notification_inbox_unread_count().hash(&mut hasher);
state.notification_inbox_mention_count().hash(&mut hasher);
hasher.finish()
}
#[cfg(test)]
mod tests {
use super::view_signature;
use crate::discord::ids::Id;
use crate::discord::{AppEvent, ChannelInfo, MessageHistoryLoadTarget};
use crate::tui::state::DashboardState;
#[test]
fn view_signature_is_stable_and_tracks_visible_changes() {
let state = DashboardState::new();
let before = view_signature(&state);
assert_eq!(before, view_signature(&state));
let mut state = state;
state.push_event(AppEvent::UpdateAvailable {
latest_version: "9.9.9".to_owned(),
});
assert_ne!(before, view_signature(&state));
}
#[test]
fn view_signature_tracks_composer_history_state_changes() {
let mut state = DashboardState::new();
state.push_event(AppEvent::ChannelUpsert(ChannelInfo::test(
Id::new(20),
"dm",
)));
state.confirm_selected_guild();
state.confirm_selected_channel();
let loading = view_signature(&state);
state.push_event(AppEvent::MessageHistoryLoaded {
channel_id: Id::new(20),
before: None,
messages: Vec::new(),
});
let loaded = view_signature(&state);
assert_ne!(loading, loaded);
state.push_event(AppEvent::MessageHistoryLoadFailed {
channel_id: Id::new(20),
target: MessageHistoryLoadTarget::Latest,
message: "offline".to_owned(),
});
assert_ne!(loaded, view_signature(&state));
}
}