use crate::{
diff, file, init, reindex_selection, repo, repo_got_change_diffs,
selection, update, ManagingRepoMsg, Msg, State,
};
use inflorescence_model::model::{
ManagingRepoSubState, ReadyState, RecordChanges, SubState,
};
use inflorescence_model::{action, model};
use inflorescence_view::view;
use libflorescence::prelude::Timestamp;
use libflorescence::testing::{
repo_path, setup_test_repo, setup_test_user_id, TestRepo, TestUserId,
DEFAULT_IGNORE_FILE, INITIAL_LOG_LEN,
};
use assert_matches::assert_matches;
use derivative::Derivative;
use iced::widget::text_editor;
use iced_utils::task::{self, await_next_msg};
use iced_utils::Task;
use test_log::test;
use tokio::fs;
use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;
#[test(tokio::test)]
async fn test_init() {
let _id = setup_test_user_id();
let repo = setup_test_repo();
let repo_path = repo_path(&repo);
let (state, mut tasks) = init(Some(repo_path.to_path_buf()));
assert_matches!(state.model.sub, model::SubState::ManagingRepo(_));
if let model::SubState::ManagingRepo(sub) = state.model.sub {
assert_eq!(sub.repo_path, repo_path);
}
let msg_0 = task::await_next_msg(&mut tasks).await;
let msg_1 = task::await_next_msg(&mut tasks).await;
let msg_2 = task::await_next_msg(&mut tasks).await;
let mut window_opened = false;
let mut loaded_id = false;
let mut inited_repo = false;
for msg in [msg_0, msg_1, msg_2] {
if !window_opened && matches!(msg, Msg::NoOp) {
window_opened = true;
continue;
}
if !loaded_id
&& matches!(
msg,
Msg::ManagingRepo(ManagingRepoMsg::LoadedIdentities(_))
)
{
loaded_id = true;
continue;
}
if !inited_repo
&& matches!(
msg,
Msg::ManagingRepo(ManagingRepoMsg::FromRepo(
repo::MsgOut::Init(_)
))
)
{
inited_repo = true;
continue;
}
}
let all = [window_opened, loaded_id, inited_repo];
if !all.iter().all(|success| *success) {
panic!("Some task didn't complete {all:#?}")
}
}
#[test(tokio::test)]
async fn test_making_records() {
let TestState {
mut state,
mut tasks,
mut fs_watch_task,
id: _,
repo,
} = setup_state().await;
let repo_path = repo_path(&repo);
{
let ready_state = get_ready_state(&state);
assert_eq!(ready_state.repo.short_log.len(), INITIAL_LOG_LEN);
}
let to_path = |raw: &str| file::Path {
raw: raw.to_string(),
is_dir: false,
};
let file_to_record = "funky.rs";
fs::write(repo_path.join(file_to_record), "music")
.await
.unwrap();
let msg = await_next_msg(&mut fs_watch_task).await;
assert_matches!(
&msg,
Msg::ManagingRepo(ManagingRepoMsg::WatchedFileChange(
path
)) if path == &repo_path.join(file_to_record)
);
let _task = update(&mut state, msg);
let msg = await_next_msg(&mut tasks).await;
assert_matches!(
&msg,
Msg::ManagingRepo(ManagingRepoMsg::FromRepo(repo::MsgOut::Refreshed {
invalidate_logs: true,
..
}))
);
let _task = update(&mut state, msg);
update(
&mut state,
Msg::View(view::Msg::UnfilteredSelection(
selection::UnfilteredMsg::Select(
selection::Select::UntrackedFile {
ix: 0,
path: to_path(file_to_record),
},
),
)),
);
let _msg = task::await_next_msg(&mut tasks).await;
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::EnterSubMenu(
model::SubMenu::Add { recursive: false },
))),
);
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::Confirm)),
);
let msg = task::await_next_msg(&mut tasks).await;
assert_matches!(
&msg,
Msg::ManagingRepo(ManagingRepoMsg::FromRepo(repo::MsgOut::AddedUntrackedFile { result:_, path })) if path == file_to_record
);
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::StartRecord)),
);
{
let ready_state = get_ready_state(&state);
assert!(ready_state.record_changes.is_some());
}
let record_msg = "Added funky music";
let _task = update(
&mut state,
Msg::View(view::Msg::EditRecordMsg(record_msg.to_string())),
);
{
let ready_state = get_ready_state(&state);
assert!(ready_state.record_changes.is_some());
assert_matches!(
ready_state.record_changes.as_ref().unwrap(),
RecordChanges::Typing { .. }
);
if let RecordChanges::Typing { msg, desc } =
ready_state.record_changes.as_ref().unwrap()
{
assert_eq!(msg.as_str(), record_msg);
assert_eq!(&desc.text(), "");
}
}
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::SaveRecord)),
);
let msg = task::await_next_msg(&mut tasks).await;
assert_matches!(
&msg,
Msg::ManagingRepo(ManagingRepoMsg::FromRepo(repo::MsgOut::Refreshed {
invalidate_logs: true,
..
}))
);
let _task = update(&mut state, msg);
{
let ready_state = get_ready_state(&state);
assert_eq!(ready_state.repo.short_log.len(), INITIAL_LOG_LEN + 1);
}
let file_to_record = "soul.rs";
fs::write(repo_path.join(file_to_record), "music")
.await
.unwrap();
let msg = await_next_msg(&mut fs_watch_task).await;
assert_matches!(
&msg,
Msg::ManagingRepo(ManagingRepoMsg::WatchedFileChange(
path
)) if path == &repo_path.join(file_to_record)
);
let _task = update(&mut state, msg);
let msg = await_next_msg(&mut tasks).await;
assert_matches!(
&msg,
Msg::ManagingRepo(ManagingRepoMsg::FromRepo(
repo::MsgOut::Refreshed { .. }
))
);
let _task = update(&mut state, msg);
update(
&mut state,
Msg::View(view::Msg::UnfilteredSelection(
selection::UnfilteredMsg::Select(
selection::Select::UntrackedFile {
ix: 0,
path: to_path(file_to_record),
},
),
)),
);
let msg = task::await_next_msg(&mut tasks).await;
let id = file::Id {
path: to_path(file_to_record),
file_kind: file::Kind::Untracked,
};
assert_matches!(
&msg,
Msg::ManagingRepo(ManagingRepoMsg::File(crate::file::Msg::LoadedSrcFile { id: loaded_id, .. }))
if *loaded_id == id
);
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::EnterSubMenu(
model::SubMenu::Add { recursive: false },
))),
);
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::Confirm)),
);
let msg = task::await_next_msg(&mut tasks).await;
assert_matches!(
&msg,
Msg::ManagingRepo(ManagingRepoMsg::FromRepo(repo::MsgOut::AddedUntrackedFile {result:_, path })) if path == file_to_record
);
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::StartRecord)),
);
{
let ready_state = get_ready_state(&state);
assert!(ready_state.record_changes.is_some());
}
let record_msg = "Added soul music";
let _task = update(
&mut state,
Msg::View(view::Msg::EditRecordMsg(record_msg.to_string())),
);
{
let ready_state = get_ready_state(&state);
assert!(ready_state.record_changes.is_some());
assert_matches!(
ready_state.record_changes.as_ref().unwrap(),
RecordChanges::Typing { .. }
);
if let RecordChanges::Typing { msg, desc } =
ready_state.record_changes.as_ref().unwrap()
{
assert_eq!(msg.as_str(), record_msg);
assert_eq!(&desc.text(), "");
}
}
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::DiscardRecord)),
);
{
let ready_state = get_ready_state(&state);
assert!(ready_state.record_changes.is_none());
}
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::StartRecord)),
);
{
let ready_state = get_ready_state(&state);
assert!(ready_state.record_changes.is_some());
}
let record_msg = "Added soul music";
let _task = update(
&mut state,
Msg::View(view::Msg::EditRecordMsg(record_msg.to_string())),
);
{
let ready_state = get_ready_state(&state);
assert!(ready_state.record_changes.is_some());
assert_matches!(
ready_state.record_changes.as_ref().unwrap(),
RecordChanges::Typing { .. }
);
if let RecordChanges::Typing { msg, desc } =
ready_state.record_changes.as_ref().unwrap()
{
assert_eq!(msg.as_str(), record_msg);
assert_eq!(&desc.text(), "");
}
}
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::PostponeRecord)),
);
{
let ready_state = get_ready_state(&state);
assert!(ready_state.record_changes.is_some());
assert_matches!(
ready_state.record_changes.as_ref().unwrap(),
RecordChanges::Canceled { old_msg, old_desc: _ } if old_msg == record_msg
);
}
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::StartRecord)),
);
{
let ready_state = get_ready_state(&state);
assert!(ready_state.record_changes.is_some());
assert!(ready_state.record_changes.is_some());
assert_matches!(
ready_state.record_changes.as_ref().unwrap(),
RecordChanges::Typing{ msg, desc: _ } if msg.as_str() == record_msg
);
}
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::DiscardRecord)),
);
{
let ready_state = get_ready_state(&state);
assert!(ready_state.record_changes.is_none());
}
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::StartRecord)),
);
{
let ready_state = get_ready_state(&state);
assert!(ready_state.record_changes.is_some());
}
let record_desc = "Added honky tonk music";
let _task = update(
&mut state,
Msg::View(view::Msg::EditRecordDesc(text_editor::Action::Edit(
text_editor::Edit::Paste(Arc::new(record_desc.to_string())),
))),
);
{
let ready_state = get_ready_state(&state);
assert!(ready_state.record_changes.is_some());
assert_matches!(
ready_state.record_changes.as_ref().unwrap(),
RecordChanges::Typing { .. }
);
if let RecordChanges::Typing { msg, desc } =
ready_state.record_changes.as_ref().unwrap()
{
assert_eq!(msg.as_str(), "");
assert_eq!(&desc.text(), record_desc);
}
}
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::PostponeRecord)),
);
{
let ready_state = get_ready_state(&state);
assert!(ready_state.record_changes.is_some());
assert_matches!(
ready_state.record_changes.as_ref().unwrap(),
RecordChanges::Canceled { old_msg: _, old_desc } if old_desc == record_desc
);
}
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::StartRecord)),
);
{
let ready_state = get_ready_state(&state);
assert!(ready_state.record_changes.is_some());
assert!(ready_state.record_changes.is_some());
assert_matches!(
ready_state.record_changes.as_ref().unwrap(),
RecordChanges::Typing{ msg: _, desc } if desc.text() == record_desc
);
}
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::DiscardRecord)),
);
{
let ready_state = get_ready_state(&state);
assert!(ready_state.record_changes.is_none());
}
}
#[test(tokio::test)]
async fn test_start_record_when_nothing_to_record() {
let TestState {
mut state,
tasks: _,
fs_watch_task: _,
id: _,
repo: _repo,
} = setup_state().await;
let task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::StartRecord)),
);
assert!(task.is_none());
{
let ready_state = get_ready_state(&state);
assert!(ready_state.record_changes.is_none());
}
}
#[test(tokio::test)]
async fn test_add_and_rm_untracked_file() {
let TestState {
mut state,
mut tasks,
mut fs_watch_task,
id: _,
repo,
} = setup_state().await;
let repo_path = repo_path(&repo);
{
let ready_state = get_ready_state(&state);
let repo_state = &ready_state.repo;
assert!(repo_state.untracked_files.is_empty());
assert!(repo_state.changed_files.is_empty());
}
let to_path = |raw: &str| file::Path {
raw: raw.to_string(),
is_dir: false,
};
let file_to_record = "new_file.rs";
fs::write(repo_path.join(file_to_record), "some contents")
.await
.unwrap();
let msg = await_next_msg(&mut fs_watch_task).await;
assert_matches!(
&msg,
Msg::ManagingRepo(ManagingRepoMsg::WatchedFileChange(
path
)) if path == &repo_path.join(file_to_record)
);
let _task = update(&mut state, msg);
let msg = await_next_msg(&mut tasks).await;
assert_matches!(
&msg,
Msg::ManagingRepo(ManagingRepoMsg::FromRepo(repo::MsgOut::Refreshed {
invalidate_logs: true,
..
}))
);
let _task = update(&mut state, msg);
{
let ready_state = get_ready_state(&state);
let repo_state = &ready_state.repo;
assert_eq!(repo_state.untracked_files.len(), 1);
assert!(repo_state
.untracked_files
.contains(&to_path(file_to_record)));
assert!(repo_state.changed_files.is_empty());
}
update(
&mut state,
Msg::View(view::Msg::UnfilteredSelection(
selection::UnfilteredMsg::Select(
selection::Select::UntrackedFile {
ix: 0,
path: to_path(file_to_record),
},
),
)),
);
let msg = task::await_next_msg(&mut tasks).await;
let id = file::Id {
path: to_path(file_to_record),
file_kind: file::Kind::Untracked,
};
assert_matches!(
&msg,
Msg::ManagingRepo(ManagingRepoMsg::File(crate::file::Msg::LoadedSrcFile { id: loaded_id, .. }))
if *loaded_id == id
);
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::EnterSubMenu(
model::SubMenu::Add { recursive: false },
))),
);
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::Confirm)),
);
let msg = task::await_next_msg(&mut tasks).await;
assert_matches!(
&msg,
Msg::ManagingRepo(ManagingRepoMsg::FromRepo(repo::MsgOut::AddedUntrackedFile { result:_, path }))
if path == file_to_record
);
{
let ready_state = get_ready_state(&state);
let repo_state = &ready_state.repo;
assert!(repo_state.untracked_files.is_empty());
assert_eq!(repo_state.changed_files.len(), 1);
assert!(repo_state
.changed_files
.contains_key(&to_path(file_to_record)));
assert_eq!(
repo_state
.changed_files
.get(&to_path(file_to_record))
.unwrap(),
&BTreeSet::from_iter([repo::ChangedFileDiff::Add {
contents: None
}])
);
}
update(
&mut state,
Msg::View(view::Msg::UnfilteredSelection(
selection::UnfilteredMsg::Select(selection::Select::ChangedFile {
ix: 0,
path: to_path(file_to_record),
}),
)),
);
let msg = task::await_next_msg(&mut tasks).await;
let id = file::Id {
path: to_path(file_to_record),
file_kind: file::Kind::Changed,
};
assert_matches!(
&msg,
Msg::ManagingRepo(ManagingRepoMsg::File(crate::file::Msg::LoadedSrcFile { id: loaded_id, .. }))
if *loaded_id == id
);
let _task = update(
&mut state,
Msg::View(view::Msg::Action(action::FilteredMsg::RmChange)),
);
let msg = task::await_next_msg(&mut tasks).await;
assert_matches!(
&msg,
Msg::ManagingRepo(ManagingRepoMsg::FromRepo(repo::MsgOut::RmedAddedFile { result:_, path }))
if path == file_to_record
);
{
let ready_state = get_ready_state(&state);
let repo_state = &ready_state.repo;
assert_eq!(repo_state.untracked_files.len(), 1);
assert!(repo_state
.untracked_files
.contains(&to_path(file_to_record)));
assert!(repo_state.changed_files.is_empty());
}
}
#[test(tokio::test)]
async fn test_reindex_selection() {
let TestState {
mut state,
tasks: _tasks,
fs_watch_task: _,
id: _,
repo: _,
} = setup_state().await;
let SubState::ManagingRepo(sub) = &mut state.model.sub else {
panic!("Unexpected state: {:?}", state.model.sub)
};
let ManagingRepoSubState::Ready(ready_state) = &mut sub.sub else {
panic!("Unexpected state: {:?}", sub.sub)
};
let to_path = |raw: &str| file::Path {
raw: raw.to_string(),
is_dir: false,
};
assert!(ready_state.selection.status.is_none());
let task = reindex_selection(
&ready_state.repo,
&mut ready_state.selection,
&mut state.managing_repo.as_mut().unwrap().files,
&ready_state.logs,
);
assert!(ready_state.selection.status.is_none());
assert!(state
.managing_repo
.as_ref()
.unwrap()
.files
.diffs_cache
.inner
.is_empty());
assert!(task.is_none());
ready_state.repo.untracked_files = BTreeSet::from_iter([
to_path("untracked_0.rs"),
to_path("untracked_1.rs"),
to_path("untracked_2.rs"),
]);
ready_state.selection.status = Some(selection::Status::UntrackedFile {
ix: 0,
path: to_path("untracked_1.rs"),
diff_selected: false,
});
let task = reindex_selection(
&ready_state.repo,
&mut ready_state.selection,
&mut state.managing_repo.as_mut().unwrap().files,
&ready_state.logs,
);
assert!(ready_state.selection.status.is_some());
assert_matches!(
ready_state.selection.status.as_ref().unwrap(),
selection::Status::UntrackedFile { ix, path, diff_selected }
if *ix == 1 && path.raw == "untracked_1.rs" && !diff_selected
);
assert!(task.is_none());
assert_eq!(
state
.managing_repo
.as_ref()
.unwrap()
.files
.diffs_cache
.inner
.len(),
1
);
assert_matches!(
state
.managing_repo
.as_ref()
.unwrap()
.files
.diffs_cache
.inner
.peek(&file::id_parts_hash(
&to_path("untracked_1.rs"),
file::Kind::Untracked
)),
Some(file::Diff::Loading)
);
state
.managing_repo
.as_mut()
.unwrap()
.files
.diffs_cache
.inner
.clear();
ready_state.selection.status = Some(selection::Status::UntrackedFile {
ix: 0,
path: to_path("untracked_gone.rs"),
diff_selected: false,
});
let task = reindex_selection(
&ready_state.repo,
&mut ready_state.selection,
&mut state.managing_repo.as_mut().unwrap().files,
&ready_state.logs,
);
assert!(ready_state.selection.status.is_none());
assert!(task.is_none());
assert!(state
.managing_repo
.as_ref()
.unwrap()
.files
.diffs_cache
.inner
.is_empty());
ready_state.repo.changed_files = repo::ChangedFiles::from_iter([
(
to_path("changed_0.rs"),
BTreeSet::from_iter([repo::ChangedFileDiff::Add {
contents: None,
}]),
),
(to_path("changed_1.rs"), BTreeSet::new()),
(to_path("changed_2.rs"), BTreeSet::new()),
]);
ready_state.selection.status = Some(selection::Status::ChangedFile {
ix: 1,
path: to_path("changed_0.rs"),
diff_selected: false,
});
let task = reindex_selection(
&ready_state.repo,
&mut ready_state.selection,
&mut state.managing_repo.as_mut().unwrap().files,
&ready_state.logs,
);
assert!(ready_state.selection.status.is_some());
assert_matches!(
ready_state.selection.status.as_ref().unwrap(),
selection::Status::ChangedFile { ix, path, diff_selected }
if *ix == 0 && path.raw == "changed_0.rs" && !diff_selected
);
assert!(task.is_none());
assert_eq!(
state
.managing_repo
.as_ref()
.unwrap()
.files
.diffs_cache
.inner
.len(),
1
);
assert_matches!(
state
.managing_repo
.as_ref()
.unwrap()
.files
.diffs_cache
.inner
.peek(&file::id_parts_hash(
&to_path("changed_0.rs"),
file::Kind::Changed
)),
Some(file::Diff::Loading)
);
state
.managing_repo
.as_mut()
.unwrap()
.files
.diffs_cache
.inner
.clear();
ready_state.selection.status = Some(selection::Status::ChangedFile {
ix: 1,
path: to_path("changed_1.rs"),
diff_selected: false,
});
{
let changed_file = BTreeSet::from_iter([repo::ChangedFileDiff::Del {
contents: None,
}]);
assert!(!diff::should_file_exist(&changed_file));
ready_state
.repo
.changed_files
.insert(to_path("changed_1.rs"), changed_file);
}
let mut task = reindex_selection(
&ready_state.repo,
&mut ready_state.selection,
&mut state.managing_repo.as_mut().unwrap().files,
&ready_state.logs,
);
assert!(ready_state.selection.status.is_some());
assert_matches!(
ready_state.selection.status.as_ref().unwrap(),
selection::Status::ChangedFile { ix, path, diff_selected }
if *ix == 1 && path.raw == "changed_1.rs" && !diff_selected
);
assert!(task.is_some());
let msg = await_next_msg(&mut task).await;
assert_matches!(
&msg,
crate::ManagingRepoMsg::File(file::Msg::SrcFileDoesntExist { .. })
);
assert_matches!(
state
.managing_repo
.as_ref()
.unwrap()
.files
.diffs_cache
.inner
.peek(&file::id_parts_hash(
&to_path("changed_1.rs"),
file::Kind::Changed
)),
Some(file::Diff::Loaded(_))
);
state
.managing_repo
.as_mut()
.unwrap()
.files
.diffs_cache
.inner
.clear();
ready_state.selection.status = Some(selection::Status::ChangedFile {
ix: 0,
path: to_path("changed_gone.rs"),
diff_selected: false,
});
let task = reindex_selection(
&ready_state.repo,
&mut ready_state.selection,
&mut state.managing_repo.as_mut().unwrap().files,
&ready_state.logs,
);
assert!(ready_state.selection.status.is_none());
assert!(task.is_none());
assert!(state
.managing_repo
.as_ref()
.unwrap()
.files
.diffs_cache
.inner
.is_empty());
let change_hash_0 = repo::hash_bytes(&[0]);
let change_hash_1 = repo::hash_bytes(&[1]);
let change_hash_2 = repo::hash_bytes(&[2]);
ready_state.repo.short_log = vec![
repo::LogEntry {
hash: change_hash_0,
message: "".to_string(),
description: None,
timestamp: Timestamp::now(),
file_paths: vec![],
},
repo::LogEntry {
hash: change_hash_1,
message: "".to_string(),
description: None,
timestamp: Timestamp::now(),
file_paths: vec![],
},
repo::LogEntry {
hash: change_hash_2,
message: "".to_string(),
description: None,
timestamp: Timestamp::now(),
file_paths: vec![],
},
];
ready_state.selection.status =
Some(selection::Status::LogChange(selection::LogChange {
ix: 0,
hash: change_hash_2,
message: "".to_string(),
file: None,
}));
let task = reindex_selection(
&ready_state.repo,
&mut ready_state.selection,
&mut state.managing_repo.as_mut().unwrap().files,
&ready_state.logs,
);
assert!(ready_state.selection.status.is_some());
assert_matches!(
ready_state.selection.status.as_ref().unwrap(),
selection::Status::LogChange(selection::LogChange { ix, hash, .. })
if *ix == 2 && *hash == change_hash_2
);
assert!(task.is_some());
assert!(state
.managing_repo
.as_ref()
.unwrap()
.files
.diffs_cache
.inner
.is_empty());
ready_state.selection.status = Some(selection::Status::UntrackedFile {
ix: 0,
path: to_path("log_gone.rs"),
diff_selected: false,
});
let task = reindex_selection(
&ready_state.repo,
&mut ready_state.selection,
&mut state.managing_repo.as_mut().unwrap().files,
&ready_state.logs,
);
assert!(ready_state.selection.status.is_none());
assert!(task.is_none());
assert!(state
.managing_repo
.as_ref()
.unwrap()
.files
.diffs_cache
.inner
.is_empty());
}
#[test(tokio::test)]
async fn test_repo_got_change_diffs() {
let TestState {
mut state,
tasks: _tasks,
fs_watch_task: _,
id: _,
repo: _,
} = setup_state().await;
let change_hash_0 = repo::hash_bytes(&[0]);
let change_hash_1 = repo::hash_bytes(&[1]);
{
let ready_state = get_ready_state(&state);
assert!(ready_state.selection.status.is_none());
}
let diffs = BTreeMap::from_iter([]);
let task = {
let model::SubState::ManagingRepo(model) = &mut state.model.sub else {
panic!("Unexpected state {:?}", state.model.sub)
};
repo_got_change_diffs(model, change_hash_0, diffs)
};
assert!(task.is_none());
{
let ready_state = get_ready_state(&state);
assert!(ready_state.selection.status.is_none());
}
{
let ready_state = get_ready_state_mut(&mut state);
ready_state.selection.status =
Some(selection::Status::LogChange(selection::LogChange {
ix: 0,
hash: change_hash_0,
message: "".to_string(),
file: None,
}));
}
let diffs = BTreeMap::from_iter([]);
let task = {
let model::SubState::ManagingRepo(model) = &mut state.model.sub else {
panic!("Unexpected state {:?}", state.model.sub)
};
repo_got_change_diffs(model, change_hash_1, diffs)
};
assert!(task.is_none());
{
let ready_state = get_ready_state(&state);
assert!(ready_state.selection.status.is_some());
}
{
let ready_state = get_ready_state_mut(&mut state);
ready_state.selection.status =
Some(selection::Status::LogChange(selection::LogChange {
ix: 0,
hash: change_hash_1,
message: "".to_string(),
file: None,
}));
}
let diffs = BTreeMap::from_iter([]);
let task = {
let model::SubState::ManagingRepo(model) = &mut state.model.sub else {
panic!("Unexpected state {:?}", state.model.sub)
};
repo_got_change_diffs(model, change_hash_1, diffs)
};
assert!(task.is_none());
{
let ready_state = get_ready_state(&state);
assert!(ready_state.selection.status.is_some());
}
}
async fn setup_state() -> TestState {
let id = setup_test_user_id();
let repo = setup_test_repo();
let repo_path = repo_path(&repo);
let (mut state, mut tasks) = init(Some(repo_path.to_path_buf()));
let mut fs_watch_task: Task<Msg> = Task::none();
for _ in 0..3 {
let msg = task::await_next_msg(&mut tasks).await;
let is_init = matches!(
&msg,
Msg::ManagingRepo(ManagingRepoMsg::FromRepo(repo::MsgOut::Init(_)))
);
let task = update(&mut state, msg);
if is_init {
fs_watch_task = task;
}
}
let ready_state = get_ready_state(&state);
let log = &ready_state.repo.short_log;
assert_eq!(log.len(), 2);
assert_eq!(log[0].file_paths, [DEFAULT_IGNORE_FILE]);
assert_eq!(log[1].file_paths, ["/"]);
TestState {
state,
tasks,
fs_watch_task,
id,
repo,
}
}
#[derive(Derivative)]
#[derivative(Debug)]
struct TestState {
pub state: State,
#[derivative(Debug = "ignore")]
pub tasks: Task<Msg>,
#[derivative(Debug = "ignore")]
pub fs_watch_task: Task<Msg>,
pub id: TestUserId,
pub repo: TestRepo,
}
#[track_caller]
fn get_ready_state(state: &State) -> &ReadyState {
let SubState::ManagingRepo(state) = &state.model.sub else {
panic!("Unexpected state: {:?}", state.model.sub)
};
let ManagingRepoSubState::Ready(ready_state) = &state.sub else {
panic!("Unexpected state: {:?}", state.sub)
};
ready_state
}
#[track_caller]
fn get_ready_state_mut(state: &mut State) -> &mut ReadyState {
let state_dbg = format!("{:?}", state.model.sub);
let SubState::ManagingRepo(state) = &mut state.model.sub else {
panic!("Unexpected state: {state_dbg}")
};
let state_dbg = format!("{:?}", state.sub);
let ManagingRepoSubState::Ready(ready_state) = &mut state.sub else {
panic!("Unexpected state: {state_dbg}")
};
ready_state
}