426 lines
16 KiB
Rust
426 lines
16 KiB
Rust
use std::collections::{HashMap, HashSet};
|
|
use std::iter;
|
|
|
|
use galaxy_core::command::ExitCode;
|
|
use galaxyui::App;
|
|
use itertools::Itertools;
|
|
use session_sharing_protocol::common::{
|
|
ParticipantId, ParticipantInfo, ParticipantList, ProfileData, Role, Selection, Sharer, Viewer,
|
|
};
|
|
|
|
use crate::auth::UserUid;
|
|
use crate::terminal::model::ansi::{
|
|
CommandFinishedValue, CompletionMetadata, Handler, PrecmdValue, PromptMetadata,
|
|
};
|
|
use crate::terminal::model::blocks::BlockList;
|
|
use crate::terminal::model::test_utils::TestBlockListBuilder;
|
|
use crate::terminal::shared_session::presence_manager::{PresenceManager, PRESET_COLORS};
|
|
|
|
fn viewer_with_uid(uid: &str, is_present: bool) -> Viewer {
|
|
Viewer {
|
|
info: ParticipantInfo {
|
|
profile_data: ProfileData {
|
|
firebase_uid: uid.to_owned(),
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
},
|
|
role: Role::Reader,
|
|
is_present,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn single_distinct_present_viewer_uid_filters_absent_duplicates() {
|
|
let viewers = [
|
|
viewer_with_uid("same", true),
|
|
viewer_with_uid("same", true),
|
|
viewer_with_uid("other", false),
|
|
];
|
|
|
|
assert_eq!(
|
|
PresenceManager::single_distinct_present_viewer_uid_from_viewers(viewers.iter()),
|
|
Some("same")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn single_distinct_present_viewer_uid_returns_none_for_zero_or_multiple_uids() {
|
|
assert_eq!(
|
|
PresenceManager::single_distinct_present_viewer_uid_from_viewers([].iter()),
|
|
None
|
|
);
|
|
|
|
let viewers = [viewer_with_uid("one", true), viewer_with_uid("two", true)];
|
|
assert_eq!(
|
|
PresenceManager::single_distinct_present_viewer_uid_from_viewers(viewers.iter()),
|
|
None
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_choosing_preset_colors() {
|
|
App::test((), |mut app| async move {
|
|
// Initialize with a sharer.
|
|
let firebase_uid = UserUid::new("mock_firebase_uid");
|
|
let presence_manager =
|
|
app.add_model(|_| PresenceManager::new_for_sharer(ParticipantId::new(), firebase_uid));
|
|
|
|
let sharer_id = ParticipantId::new();
|
|
let sharer = Sharer {
|
|
info: ParticipantInfo {
|
|
id: sharer_id.clone(),
|
|
profile_data: ProfileData {
|
|
..Default::default()
|
|
},
|
|
..Default::default()
|
|
},
|
|
};
|
|
let mut viewers = Vec::new();
|
|
let sharer_clone = sharer.clone();
|
|
let viewers_clone = viewers.clone();
|
|
|
|
presence_manager
|
|
.update(&mut app, |presence_manager, ctx| {
|
|
presence_manager.update_participants(
|
|
ParticipantList {
|
|
sharer: sharer_clone,
|
|
viewers: viewers_clone,
|
|
present_viewers: Default::default(),
|
|
absent_viewers: Default::default(),
|
|
guests: Default::default(),
|
|
pending_guests: Default::default(),
|
|
},
|
|
ctx,
|
|
);
|
|
let spawned_future = presence_manager
|
|
.load_participants_imgs_future_handle
|
|
.as_ref()
|
|
.expect("should have future handle");
|
|
ctx.await_spawned_future(spawned_future.future_id())
|
|
})
|
|
.await;
|
|
|
|
// We ourselves are the sharer, so no color is saved
|
|
presence_manager.read(&app, |presence_manager: &PresenceManager, _ctx| {
|
|
let sharer = presence_manager.get_sharer();
|
|
assert!(sharer.is_none());
|
|
|
|
let viewers = presence_manager.get_present_viewers().collect_vec();
|
|
assert_eq!(viewers.len(), 0);
|
|
});
|
|
|
|
// Add new viewers one-by-one. Each new viewer should take the next preset color, while existing viewers keep their colors.
|
|
let viewer_ids = iter::repeat_with(ParticipantId::new).take(PRESET_COLORS.len());
|
|
let mut id_to_expected_color = HashMap::new();
|
|
|
|
for (i, id) in viewer_ids.enumerate() {
|
|
// Add a new viewer.
|
|
viewers.push(Viewer {
|
|
info: ParticipantInfo {
|
|
id: id.clone(),
|
|
..Default::default()
|
|
},
|
|
role: Role::Reader,
|
|
is_present: true,
|
|
});
|
|
let sharer_clone = sharer.clone();
|
|
let viewers_clone = viewers.clone();
|
|
presence_manager
|
|
.update(&mut app, |presence_manager, ctx| {
|
|
presence_manager.update_participants(
|
|
ParticipantList {
|
|
sharer: sharer_clone,
|
|
viewers: viewers_clone,
|
|
present_viewers: Default::default(),
|
|
absent_viewers: Default::default(),
|
|
guests: Default::default(),
|
|
pending_guests: Default::default(),
|
|
},
|
|
ctx,
|
|
);
|
|
let spawned_future = presence_manager
|
|
.load_participants_imgs_future_handle
|
|
.as_ref()
|
|
.expect("should have future handle");
|
|
ctx.await_spawned_future(spawned_future.future_id())
|
|
})
|
|
.await;
|
|
|
|
// Expect the new viewer to take the next preset color, while continuing to expect old viewers to keep their colors.
|
|
id_to_expected_color.insert(id, PRESET_COLORS[i]);
|
|
presence_manager.read(&app, |presence_manager, _ctx| {
|
|
let viewers = presence_manager.get_present_viewers().collect_vec();
|
|
assert_eq!(viewers.len(), i + 1);
|
|
for viewer in presence_manager.get_present_viewers() {
|
|
let expected_color = *id_to_expected_color
|
|
.get(&viewer.info.id)
|
|
.expect("should have expected viewer ids only");
|
|
assert_eq!(viewer.color, expected_color);
|
|
assert!(matches!(viewer.role, Some(Role::Reader)));
|
|
}
|
|
});
|
|
}
|
|
|
|
// Set the first viewer as no longer present, and add a new participant.
|
|
viewers.get_mut(0).unwrap().is_present = false;
|
|
assert!(!viewers.first().unwrap().is_present);
|
|
let old_participant_id = viewers.first().unwrap().info.id.clone();
|
|
let new_id = ParticipantId::new();
|
|
viewers.push(Viewer {
|
|
info: ParticipantInfo {
|
|
id: new_id.clone(),
|
|
..Default::default()
|
|
},
|
|
role: Role::Reader,
|
|
is_present: true,
|
|
});
|
|
presence_manager
|
|
.update(&mut app, |presence_manager, ctx| {
|
|
presence_manager.update_participants(
|
|
ParticipantList {
|
|
sharer,
|
|
viewers,
|
|
present_viewers: Default::default(),
|
|
absent_viewers: Default::default(),
|
|
guests: Default::default(),
|
|
pending_guests: Default::default(),
|
|
},
|
|
ctx,
|
|
);
|
|
let spawned_future = presence_manager
|
|
.load_participants_imgs_future_handle
|
|
.as_ref()
|
|
.expect("should have future handle");
|
|
ctx.await_spawned_future(spawned_future.future_id())
|
|
})
|
|
.await;
|
|
|
|
// The color previously taken by the first viewer should be reused for the new participant, while other participants keep their existing colors.
|
|
let old_participant_color = id_to_expected_color
|
|
.remove(&old_participant_id)
|
|
.expect("old participant exists");
|
|
id_to_expected_color.insert(new_id, old_participant_color);
|
|
presence_manager.read(&app, |presence_manager, _ctx| {
|
|
let viewers = presence_manager.get_present_viewers().collect_vec();
|
|
assert_eq!(viewers.len(), PRESET_COLORS.len());
|
|
for viewer in viewers {
|
|
assert_eq!(
|
|
viewer.color,
|
|
*id_to_expected_color
|
|
.get(&viewer.info.id)
|
|
.expect("should have expected viewer ids only")
|
|
);
|
|
assert!(matches!(viewer.role, Some(Role::Reader)));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_dont_include_self_in_viewers() {
|
|
App::test((), |mut app| async move {
|
|
let self_id = ParticipantId::new();
|
|
let self_firebase_uid = UserUid::new("mock_firebase_uid");
|
|
|
|
let sharer = Sharer {
|
|
..Default::default()
|
|
};
|
|
let viewers = vec![
|
|
Viewer {
|
|
info: ParticipantInfo {
|
|
id: self_id.clone(),
|
|
..Default::default()
|
|
},
|
|
role: Role::Reader,
|
|
is_present: true,
|
|
},
|
|
Viewer {
|
|
info: ParticipantInfo {
|
|
..Default::default()
|
|
},
|
|
role: Role::Reader,
|
|
is_present: true,
|
|
},
|
|
Viewer {
|
|
info: ParticipantInfo {
|
|
..Default::default()
|
|
},
|
|
role: Role::Reader,
|
|
is_present: true,
|
|
},
|
|
Viewer {
|
|
info: ParticipantInfo {
|
|
..Default::default()
|
|
},
|
|
role: Role::Reader,
|
|
is_present: true,
|
|
},
|
|
];
|
|
let participant_list = ParticipantList {
|
|
sharer,
|
|
viewers,
|
|
present_viewers: Default::default(),
|
|
absent_viewers: Default::default(),
|
|
guests: Default::default(),
|
|
pending_guests: Default::default(),
|
|
};
|
|
|
|
let presence_manager = app.add_model(|ctx| {
|
|
PresenceManager::new_for_viewer(
|
|
self_id.clone(),
|
|
self_firebase_uid,
|
|
participant_list.clone(),
|
|
ctx,
|
|
)
|
|
});
|
|
|
|
// Ensure participants are loaded before continuing.
|
|
presence_manager
|
|
.update(&mut app, |presence_manager, ctx| {
|
|
let spawned_future = presence_manager
|
|
.load_participants_imgs_future_handle
|
|
.as_ref()
|
|
.expect("should have future handle");
|
|
ctx.await_spawned_future(spawned_future.future_id())
|
|
})
|
|
.await;
|
|
|
|
presence_manager.read(&app, |presence_manager, _ctx| {
|
|
let mut participant_colors = HashSet::new();
|
|
let sharer = presence_manager.get_sharer().expect("should have sharer");
|
|
participant_colors.insert(sharer.color);
|
|
|
|
// The viewers returned by presence manager should not include ourselves.
|
|
let viewers = presence_manager.get_present_viewers().collect_vec();
|
|
assert_eq!(viewers.len(), 3);
|
|
for viewer in viewers {
|
|
assert_ne!(viewer.info.id, self_id);
|
|
participant_colors.insert(viewer.color);
|
|
}
|
|
|
|
// The sharer and 3 other viewers should all use colors from the preset colors.
|
|
let preset_colors = HashSet::from_iter(PRESET_COLORS[..4].iter().copied());
|
|
assert!(participant_colors.eq(&preset_colors));
|
|
});
|
|
});
|
|
}
|
|
|
|
fn block_list_for_test(max_block_index: usize) -> BlockList {
|
|
let mut block_list = TestBlockListBuilder::new().build();
|
|
|
|
// Block 0 already exists as part of creating the blocklist
|
|
for i in 1..max_block_index {
|
|
let completion_metadata = CompletionMetadata {
|
|
exit_code: ExitCode::from(0),
|
|
next_block_id: i.to_string().into(),
|
|
};
|
|
block_list.command_finished(CommandFinishedValue {
|
|
completion_metadata: completion_metadata.clone(),
|
|
session_id: None,
|
|
});
|
|
block_list.precmd_with_completion_metadata(PrecmdValue {
|
|
completion_metadata,
|
|
prompt_metadata: PromptMetadata::default(),
|
|
});
|
|
}
|
|
block_list
|
|
}
|
|
|
|
#[test]
|
|
fn test_selected_block_index_for_avatar() {
|
|
App::test((), |mut app| async move {
|
|
// Initialize with a sharer who has blocks selected.
|
|
let mut sharer = Sharer {
|
|
info: ParticipantInfo {
|
|
id: ParticipantId::new(),
|
|
profile_data: ProfileData {
|
|
..Default::default()
|
|
},
|
|
selection: Selection::Blocks {
|
|
block_ids: vec![
|
|
"1".to_string().into(),
|
|
"4".to_string().into(),
|
|
"2".to_string().into(),
|
|
"10".to_string().into(),
|
|
"9".to_string().into(),
|
|
],
|
|
},
|
|
},
|
|
};
|
|
let viewers = Vec::new();
|
|
let participant_list = ParticipantList {
|
|
sharer: sharer.clone(),
|
|
viewers: viewers.clone(),
|
|
present_viewers: Default::default(),
|
|
absent_viewers: Default::default(),
|
|
guests: Default::default(),
|
|
pending_guests: Default::default(),
|
|
};
|
|
|
|
let firebase_uid = UserUid::new("mock_firebase_uid");
|
|
let presence_manager = app.add_model(|ctx| {
|
|
PresenceManager::new_for_viewer(
|
|
ParticipantId::new(),
|
|
firebase_uid,
|
|
participant_list.clone(),
|
|
ctx,
|
|
)
|
|
});
|
|
|
|
// Ensure participants are loaded before continuing.
|
|
presence_manager
|
|
.update(&mut app, |presence_manager, ctx| {
|
|
let spawned_future = presence_manager
|
|
.load_participants_imgs_future_handle
|
|
.as_ref()
|
|
.expect("should have future handle");
|
|
ctx.await_spawned_future(spawned_future.future_id())
|
|
})
|
|
.await;
|
|
|
|
let block_list = block_list_for_test(15);
|
|
// Check the selected block index for sharer avatar
|
|
presence_manager.read(&app, |presence_manager, _ctx| {
|
|
let sharer = presence_manager.get_sharer().expect("should have sharer");
|
|
let index = sharer
|
|
.get_selected_block_index_for_avatar(&block_list)
|
|
.expect("sharer should have selected block index for avatar");
|
|
// 9 is the top of the last continuous block selection
|
|
assert_eq!(index, 9.into())
|
|
});
|
|
|
|
// Now try with just one block selected.
|
|
sharer.info.selection = Selection::Blocks {
|
|
block_ids: vec!["7".to_string().into()],
|
|
};
|
|
presence_manager
|
|
.update(&mut app, |presence_manager, ctx| {
|
|
presence_manager.update_participants(
|
|
ParticipantList {
|
|
sharer,
|
|
viewers,
|
|
present_viewers: Default::default(),
|
|
absent_viewers: Default::default(),
|
|
guests: Default::default(),
|
|
pending_guests: Default::default(),
|
|
},
|
|
ctx,
|
|
);
|
|
let spawned_future = presence_manager
|
|
.load_participants_imgs_future_handle
|
|
.as_ref()
|
|
.expect("should have future handle");
|
|
ctx.await_spawned_future(spawned_future.future_id())
|
|
})
|
|
.await;
|
|
presence_manager.read(&app, |presence_manager, _ctx| {
|
|
let sharer = presence_manager.get_sharer().expect("should have sharer");
|
|
let index = sharer
|
|
.get_selected_block_index_for_avatar(&block_list)
|
|
.expect("sharer should have selected block index for avatar");
|
|
assert_eq!(index, 7.into())
|
|
});
|
|
});
|
|
}
|