Phase 5: Projekt- und Dokumentmodell implementieren und archivieren
This commit is contained in:
489
crates/tb-ide/tests/documents.rs
Normal file
489
crates/tb-ide/tests/documents.rs
Normal file
@@ -0,0 +1,489 @@
|
||||
use std::{
|
||||
fs,
|
||||
path::PathBuf,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
use tb_ide::documents::{Decision, Destination, Project, SavePlan, StartupRemoval};
|
||||
use tb_vm::project_io::{Content, SourceLoader};
|
||||
struct Temp(PathBuf);
|
||||
impl Temp {
|
||||
fn new() -> Self {
|
||||
static N: AtomicUsize = AtomicUsize::new(0);
|
||||
let p = std::env::temp_dir().join(format!(
|
||||
"tb-documents-{}-{}",
|
||||
std::process::id(),
|
||||
N.fetch_add(1, Ordering::Relaxed)
|
||||
));
|
||||
fs::create_dir_all(&p).unwrap();
|
||||
Self(p.canonicalize().unwrap())
|
||||
}
|
||||
fn write(&self, name: &str, text: &str) -> PathBuf {
|
||||
let p = self.0.join(name);
|
||||
fs::create_dir_all(p.parent().unwrap()).unwrap();
|
||||
fs::write(&p, text).unwrap();
|
||||
p
|
||||
}
|
||||
fn plan(&self, name: &str) -> SavePlan {
|
||||
SavePlan {
|
||||
project: Some(Destination::new(self.0.join(name))),
|
||||
..SavePlan::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Drop for Temp {
|
||||
fn drop(&mut self) {
|
||||
let _ = fs::remove_dir_all(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_create_save_reopen_preserves_forms_members_startup_and_cli_sources() {
|
||||
let t = Temp::new();
|
||||
let mut p = Project::new(&t.0).unwrap();
|
||||
let a = p.new_module("Main").unwrap();
|
||||
let b = p.new_module("Lib").unwrap();
|
||||
let form = p.new_form("Form1").unwrap();
|
||||
p.replace_text(a, 0..0, "CALL Answer\nEND\n").unwrap();
|
||||
p.replace_text(b, 0..0, "SUB Answer\nPRINT 7\nEND SUB\n")
|
||||
.unwrap();
|
||||
p.edit_form(form,|f| {let parsed=tb_ui::frm::read_text("f.frm","VERSION 1.00\nBegin Form Form1\n Begin CommandButton Button1\n Index = 0\n End\n Begin CommandButton Button1\n Index = 2\n End\nEnd\n\nSUB Button1_Click(Index AS INTEGER)\nEND SUB\n")?;*f=parsed;Ok(())}).unwrap();
|
||||
p.set_startup(Some(a)).unwrap();
|
||||
let mut plan = t.plan("p.mak");
|
||||
for (id, name) in [(a, "Main.bas"), (b, "Lib.bas"), (form, "Form1.frm")] {
|
||||
plan.files.insert(id, Destination::new(t.0.join(name)));
|
||||
}
|
||||
p.save_project(&plan).unwrap();
|
||||
assert!(!p.is_dirty());
|
||||
let restored = Project::open(&t.0.join("p.mak"), vec![]).unwrap();
|
||||
assert_eq!(restored.members().len(), 3);
|
||||
assert_eq!(
|
||||
restored
|
||||
.document(restored.startup().unwrap())
|
||||
.unwrap()
|
||||
.path()
|
||||
.unwrap()
|
||||
.file_name()
|
||||
.unwrap(),
|
||||
"Main.bas"
|
||||
);
|
||||
let input = restored.sources().unwrap();
|
||||
assert_eq!(input.forms[0].root.children.len(), 2);
|
||||
assert!(input.forms[0].code.contains("Button1_Click"));
|
||||
assert!(fs::read_to_string(t.0.join("Form1.frm"))
|
||||
.unwrap()
|
||||
.lines()
|
||||
.any(|line| line.split_whitespace().collect::<Vec<_>>() == ["Index", "=", "0"]));
|
||||
let disk = SourceLoader::default().load(&t.0.join("p.mak")).unwrap();
|
||||
assert_eq!(disk.forms, input.forms);
|
||||
let mut catalog = tb_frontend::forms::FormCatalog::default();
|
||||
for f in &disk.forms {
|
||||
catalog.append(&f.catalog());
|
||||
}
|
||||
tb_vm::compile_project("P", &disk.units, &catalog, &disk.forms).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn unrepresentable_save_paths_fail_before_writing_or_changing_documents() {
|
||||
use std::{ffi::OsString, os::unix::ffi::OsStringExt};
|
||||
let t = Temp::new();
|
||||
let mut p = Project::new(&t.0).unwrap();
|
||||
let id = p.new_module("Main").unwrap();
|
||||
p.replace_text(id, 0..0, "PRINT 1\n").unwrap();
|
||||
for name in [
|
||||
OsString::from_vec(b"bad\xff.bas".to_vec()),
|
||||
"bad\n.bas".into(),
|
||||
] {
|
||||
let target = t.0.join(name);
|
||||
assert!(p.save_file(id, Some(&Destination::new(&target))).is_err());
|
||||
assert!(!target.exists());
|
||||
assert!(p.document(id).unwrap().path().is_none());
|
||||
assert!(p.document(id).unwrap().is_dirty());
|
||||
assert_eq!(p.document(id).unwrap().code(), "PRINT 1\n");
|
||||
}
|
||||
let manifest = tb_vm::project_io::Manifest {
|
||||
lines: vec![tb_vm::project_io::ProjectLine::File(
|
||||
t.0.join(OsString::from_vec(b"bad\xff.bas".to_vec())),
|
||||
)],
|
||||
startup: None,
|
||||
};
|
||||
assert!(manifest.text(&t.0.join("p.mak")).is_err());
|
||||
assert_eq!(fs::read_dir(&t.0).unwrap().count(), 0);
|
||||
}
|
||||
#[test]
|
||||
fn shared_views_text_import_and_undo_are_document_transactions() {
|
||||
let t = Temp::new();
|
||||
let mut p = Project::new(&t.0).unwrap();
|
||||
let id = p.new_module("Main").unwrap();
|
||||
p.replace_text(id, 0..0, "äbcdef\n").unwrap();
|
||||
let a = p.open_view(id).unwrap();
|
||||
let b = p.open_view(id).unwrap();
|
||||
p.view_mut(a).unwrap().cursor = 2;
|
||||
p.view_mut(b).unwrap().cursor = 7;
|
||||
p.view_mut(b).unwrap().scroll_line = 9;
|
||||
let import = t.write("text.txt", "HELLO");
|
||||
p.load_text(id, 2, &import).unwrap();
|
||||
assert_eq!(p.view_code(a).unwrap(), "äHELLObcdef\n");
|
||||
assert_eq!(p.view_code(a).unwrap(), p.view_code(b).unwrap());
|
||||
assert_eq!(p.view(b).unwrap().cursor, 12);
|
||||
assert_eq!(p.view(a).unwrap().cursor, 2);
|
||||
assert!(p.undo(id).unwrap());
|
||||
assert_eq!(p.document(id).unwrap().code(), "äbcdef\n");
|
||||
assert_eq!(p.view(b).unwrap().cursor, 7);
|
||||
assert_eq!(p.view(b).unwrap().scroll_line, 9);
|
||||
p.replace_text(id, 2..7, "").unwrap();
|
||||
assert_eq!(p.view(b).unwrap().cursor, 2);
|
||||
p.undo(id).unwrap();
|
||||
p.close_view(a).unwrap();
|
||||
assert!(p.document(id).is_ok());
|
||||
assert_eq!(p.view_code(b).unwrap(), "äbcdef\n");
|
||||
let rev = p.document(id).unwrap().revision();
|
||||
assert!(p.replace_text(id, 1..2, "bad").is_err());
|
||||
assert_eq!(p.document(id).unwrap().revision(), rev);
|
||||
p.save_text(id, Some(0..2), &Destination::new(t.0.join("selection.txt")))
|
||||
.unwrap();
|
||||
assert_eq!(fs::read_to_string(t.0.join("selection.txt")).unwrap(), "ä");
|
||||
}
|
||||
#[test]
|
||||
fn edited_include_wins_in_both_modules_without_writing_the_expansion() {
|
||||
let t = Temp::new();
|
||||
let path = t.write("p.mak", "a.bas\nb.bas\n");
|
||||
t.write("a.bas", "'$INCLUDE: 'shared.bi'\nEND\n");
|
||||
t.write("b.bas", "'$INCLUDE: 'shared.bi'\n");
|
||||
let file = t.write("shared.bi", "CONST N=1\n");
|
||||
let mut p = Project::open(&path, vec![]).unwrap();
|
||||
let id = p.open_document(&file).unwrap();
|
||||
assert_eq!(p.open_document(&file).unwrap(), id);
|
||||
p.replace_text(id, 8..9, "9").unwrap();
|
||||
let input = p.sources().unwrap();
|
||||
for u in input.units {
|
||||
assert!(u.segments.iter().any(|s| s.text == "CONST N=9\n"));
|
||||
}
|
||||
assert_eq!(fs::read_to_string(&file).unwrap(), "CONST N=1\n");
|
||||
p.save_file(id, None).unwrap();
|
||||
assert_eq!(fs::read_to_string(&file).unwrap(), "CONST N=9\n");
|
||||
assert_eq!(
|
||||
fs::read_to_string(t.0.join("a.bas")).unwrap(),
|
||||
"'$INCLUDE: 'shared.bi'\nEND\n"
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn failed_open_add_and_cancel_do_not_replace_existing_documents() {
|
||||
let t = Temp::new();
|
||||
let mut p = Project::new(&t.0).unwrap();
|
||||
let id = p.new_module("Current").unwrap();
|
||||
p.replace_text(id, 0..0, "PRINT 1\n").unwrap();
|
||||
let cyclic = t.write("cyclic.bas", "'$INCLUDE: 'cyclic.bas'\n");
|
||||
let missing = t.write("bad.mak", "missing.bas\n");
|
||||
for path in [cyclic.clone(), missing] {
|
||||
assert!(p.open_project(&path, Decision::Discard).is_err());
|
||||
assert_eq!(p.document(id).unwrap().code(), "PRINT 1\n");
|
||||
}
|
||||
assert!(p.add_file(&cyclic).is_err());
|
||||
assert_eq!(p.members(), vec![id]);
|
||||
assert!(!p.new_project(Decision::Cancel).unwrap());
|
||||
assert!(!p.prepare_close(Decision::Cancel).unwrap());
|
||||
assert!(p.document(id).unwrap().is_dirty());
|
||||
let valid = t.write("valid.bas", "END\n");
|
||||
assert!(!p.open_project(&valid, Decision::Cancel).unwrap());
|
||||
assert!(p.open_project(&valid, Decision::Discard).unwrap());
|
||||
assert!(p.document(id).is_err());
|
||||
}
|
||||
#[test]
|
||||
fn startup_removal_requires_explicit_replacement_and_never_deletes_a_file() {
|
||||
let t = Temp::new();
|
||||
let path = t.write("p.mak", "' header\na.bas\nb.bas\n");
|
||||
let a_path = t.write("a.bas", "END\n");
|
||||
t.write("b.bas", "");
|
||||
let mut p = Project::open(&path, vec![]).unwrap();
|
||||
let ids = p.members();
|
||||
p.set_startup(Some(ids[0])).unwrap();
|
||||
assert!(p.remove_file(ids[0], None).is_err());
|
||||
assert!(!p.remove_file(ids[0], Some(StartupRemoval::Cancel)).unwrap());
|
||||
assert_eq!(p.members(), ids);
|
||||
assert!(p
|
||||
.remove_file(ids[0], Some(StartupRemoval::Replace(ids[0])))
|
||||
.is_err());
|
||||
assert_eq!(p.startup(), Some(ids[0]));
|
||||
p.remove_file(ids[0], Some(StartupRemoval::Replace(ids[1])))
|
||||
.unwrap();
|
||||
assert_eq!(p.startup(), Some(ids[1]));
|
||||
assert!(a_path.exists());
|
||||
assert!(p.document(ids[0]).is_ok());
|
||||
p.save_project(&SavePlan::default()).unwrap();
|
||||
let saved = fs::read_to_string(path).unwrap();
|
||||
assert!(saved.contains("' header"));
|
||||
assert!(!saved.lines().any(|l| l == "a.bas"));
|
||||
p.remove_file(ids[1], Some(StartupRemoval::Default))
|
||||
.unwrap();
|
||||
assert_eq!(p.startup(), None);
|
||||
}
|
||||
#[test]
|
||||
fn external_changes_and_save_as_collisions_need_explicit_decisions() {
|
||||
let t = Temp::new();
|
||||
let file = t.write("main.bas", "old\n");
|
||||
let mut p = Project::open(&file, vec![]).unwrap();
|
||||
let id = p.members()[0];
|
||||
p.replace_text(id, 0..3, "new").unwrap();
|
||||
fs::write(&file, "external\n").unwrap();
|
||||
assert!(p
|
||||
.save_file(id, None)
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("Überschreibentscheidung"));
|
||||
assert_eq!(fs::read_to_string(&file).unwrap(), "external\n");
|
||||
assert!(p.document(id).unwrap().is_dirty());
|
||||
p.save_file(
|
||||
id,
|
||||
Some(&Destination {
|
||||
path: file.clone(),
|
||||
overwrite: true,
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(fs::read_to_string(&file).unwrap(), "new\n");
|
||||
let other = t.write("other.bas", "keep\n");
|
||||
assert!(p.save_file(id, Some(&Destination::new(&other))).is_err());
|
||||
assert_eq!(p.document(id).unwrap().path(), Some(file.as_path()));
|
||||
p.save_file(
|
||||
id,
|
||||
Some(&Destination {
|
||||
path: other.clone(),
|
||||
overwrite: true,
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(fs::read_to_string(&other).unwrap(), "new\n");
|
||||
assert_eq!(p.document(id).unwrap().path(), Some(other.as_path()));
|
||||
assert!(p
|
||||
.save_text(
|
||||
id,
|
||||
None,
|
||||
&Destination {
|
||||
path: other,
|
||||
overwrite: true
|
||||
}
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
#[test]
|
||||
fn partial_save_preserves_unsaved_data_and_does_not_finish_close() {
|
||||
let t = Temp::new();
|
||||
let path = t.write("p.mak", "a.bas\nb.bas\n");
|
||||
let a = t.write("a.bas", "old A\n");
|
||||
let b = t.write("b.bas", "old B\n");
|
||||
let mut p = Project::open(&path, vec![]).unwrap();
|
||||
let ids = p.members();
|
||||
for id in &ids {
|
||||
p.replace_text(*id, 0..3, "new").unwrap();
|
||||
}
|
||||
let mut perms = fs::metadata(&b).unwrap().permissions();
|
||||
perms.set_readonly(true);
|
||||
fs::set_permissions(&b, perms).unwrap();
|
||||
let result = p.prepare_close(Decision::Save(&SavePlan::default()));
|
||||
assert!(result.is_err());
|
||||
assert!(!p.document(ids[0]).unwrap().is_dirty());
|
||||
assert!(p.document(ids[1]).unwrap().is_dirty());
|
||||
assert!(p.is_dirty());
|
||||
assert_eq!(p.path(), Some(path.as_path()));
|
||||
assert_eq!(fs::read_to_string(&a).unwrap(), "new A\n");
|
||||
assert_eq!(fs::read_to_string(&b).unwrap(), "old B\n");
|
||||
assert_eq!(fs::read_to_string(&path).unwrap(), "a.bas\nb.bas\n");
|
||||
// Die ursprünglichen Schreibrechte aus einer normalen Datei wiederherstellen.
|
||||
fs::set_permissions(&b, fs::metadata(&a).unwrap().permissions()).unwrap();
|
||||
assert!(p
|
||||
.prepare_close(Decision::Save(&SavePlan::default()))
|
||||
.unwrap());
|
||||
assert!(!p.is_dirty());
|
||||
assert!(fs::read_dir(&t.0).unwrap().all(|e| !e
|
||||
.unwrap()
|
||||
.file_name()
|
||||
.to_string_lossy()
|
||||
.starts_with(".tb-save-")));
|
||||
}
|
||||
#[test]
|
||||
fn project_save_as_rebases_members_and_keeps_include_targets() {
|
||||
let t = Temp::new();
|
||||
let path = t.write("old/p.mak", "main.bas\n' retained\n");
|
||||
t.write("old/main.bas", "'$INCLUDE: 'inner.bi'\nEND\n");
|
||||
t.write("old/inner.bi", "PRINT 3\n");
|
||||
fs::create_dir(t.0.join("new")).unwrap();
|
||||
let mut p = Project::open(&path, vec![]).unwrap();
|
||||
let before = p.sources().unwrap();
|
||||
let target = t.0.join("new/q.mak");
|
||||
p.save_project(&t.plan("new/q.mak")).unwrap();
|
||||
assert_eq!(p.path(), Some(target.as_path()));
|
||||
let reopened = Project::open(&target, vec![]).unwrap();
|
||||
assert_eq!(
|
||||
before.units[0]
|
||||
.segments
|
||||
.iter()
|
||||
.map(|s| &s.text)
|
||||
.collect::<Vec<_>>(),
|
||||
reopened.sources().unwrap().units[0]
|
||||
.segments
|
||||
.iter()
|
||||
.map(|s| &s.text)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
assert!(fs::read_to_string(&target)
|
||||
.unwrap()
|
||||
.contains("../old/main.bas"));
|
||||
let bad = t.plan("missing/q.mak");
|
||||
assert!(p.save_project(&bad).is_err());
|
||||
assert_eq!(p.path(), Some(target.as_path()));
|
||||
}
|
||||
#[test]
|
||||
fn binary_import_only_saves_to_explicit_text_target_and_preserves_original_forever() {
|
||||
let t = Temp::new();
|
||||
let bytes: Vec<u8> = include_str!("../../tb-ui/tests/data/new.frm.hex")
|
||||
.split_whitespace()
|
||||
.map(|s| u8::from_str_radix(s, 16).unwrap())
|
||||
.collect();
|
||||
let binary = t.0.join("source.frm");
|
||||
fs::write(&binary, &bytes).unwrap();
|
||||
let mut p = Project::open(&binary, vec![]).unwrap();
|
||||
let id = p.members()[0];
|
||||
p.replace_text(id, 0..0, "SUB Form_Load\nEND SUB\n")
|
||||
.unwrap();
|
||||
assert!(p
|
||||
.save_file(id, None)
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("binäre FRM"));
|
||||
let text = t.0.join("converted.frm");
|
||||
p.save_file(id, Some(&Destination::new(&text))).unwrap();
|
||||
assert!(fs::read_to_string(&text).unwrap().starts_with("VERSION"));
|
||||
assert_eq!(fs::read(&binary).unwrap(), bytes);
|
||||
assert!(p
|
||||
.save_file(
|
||||
id,
|
||||
Some(&Destination {
|
||||
path: binary.clone(),
|
||||
overwrite: true
|
||||
})
|
||||
)
|
||||
.is_err());
|
||||
let original = p.document(id).unwrap().content().clone();
|
||||
p.edit_form(id, |f| {
|
||||
f.root.name = "Edited".into();
|
||||
f.code.push_str("' changed\n");
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
assert!(p.undo(id).unwrap());
|
||||
assert_eq!(p.document(id).unwrap().content(), &original);
|
||||
let mut plan = t.plan("converted.mak");
|
||||
p.save_project(&plan).unwrap();
|
||||
plan.project = None;
|
||||
let restored = Project::open(&t.0.join("converted.mak"), vec![]).unwrap();
|
||||
assert!(matches!(
|
||||
restored.document(restored.members()[0]).unwrap().content(),
|
||||
Content::Form(_)
|
||||
));
|
||||
}
|
||||
#[test]
|
||||
fn save_plan_prevents_same_destination_and_project_overwrite() {
|
||||
let t = Temp::new();
|
||||
let mut p = Project::new(&t.0).unwrap();
|
||||
let a = p.new_module("A").unwrap();
|
||||
let b = p.new_module("B").unwrap();
|
||||
let mut plan = t.plan("p.mak");
|
||||
let target = t.0.join("same.bas");
|
||||
plan.files.insert(a, Destination::new(&target));
|
||||
plan.files.insert(b, Destination::new(&target));
|
||||
assert!(p.save_project(&plan).is_err());
|
||||
assert!(!target.exists());
|
||||
assert!(p.document(a).unwrap().is_dirty());
|
||||
assert!(p
|
||||
.save_file(a, Some(&Destination::new(t.0.join("bad.frm"))))
|
||||
.is_err());
|
||||
assert!(p.new_module("../bad").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_save_as_rebases_includes_and_failed_save_does_not_change_identity_or_text() {
|
||||
let t = Temp::new();
|
||||
let path = t.write("old/p.mak", "main.bas\n");
|
||||
let original = "'$INCLUDE: 'common.bi' ' keep comment\nEND\n";
|
||||
t.write("old/main.bas", original);
|
||||
t.write("old/common.bi", "PRINT 42\n");
|
||||
fs::create_dir(t.0.join("new")).unwrap();
|
||||
let mut p = Project::open(&path, vec![]).unwrap();
|
||||
let id = p.members()[0];
|
||||
let old = p.document(id).unwrap().source_path().to_path_buf();
|
||||
assert!(p
|
||||
.save_file(id, Some(&Destination::new(t.0.join("missing/main.bas"))))
|
||||
.is_err());
|
||||
assert_eq!(p.document(id).unwrap().source_path(), old);
|
||||
assert_eq!(p.document(id).unwrap().code(), original);
|
||||
let view = p.open_view(id).unwrap();
|
||||
p.save_file(id, Some(&Destination::new(t.0.join("new/main.bas"))))
|
||||
.unwrap();
|
||||
assert!(p
|
||||
.document(id)
|
||||
.unwrap()
|
||||
.code()
|
||||
.contains("../old/common.bi' ' keep comment"));
|
||||
assert_eq!(p.view(view).unwrap().document(), id);
|
||||
p.save_project(&SavePlan::default()).unwrap();
|
||||
let restored = Project::open(&path, vec![]).unwrap();
|
||||
assert!(restored.sources().unwrap().units[0]
|
||||
.segments
|
||||
.iter()
|
||||
.any(|s| s.text == "PRINT 42\n"));
|
||||
assert_eq!(fs::read_to_string(old).unwrap(), original);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aliases_share_documents_and_form_code_offsets_match_saved_text() {
|
||||
let t = Temp::new();
|
||||
let file = t.write("Mixed.bas", "PRINT 1\n");
|
||||
let mut p = Project::new(&t.0).unwrap();
|
||||
let a = p.add_file(&file).unwrap();
|
||||
assert_eq!(p.open_document(&t.0.join("MIXED.BAS")).unwrap(), a);
|
||||
#[cfg(unix)]
|
||||
{
|
||||
std::os::unix::fs::symlink(&file, t.0.join("alias.bas")).unwrap();
|
||||
assert_eq!(p.open_document(&t.0.join("alias.bas")).unwrap(), a);
|
||||
}
|
||||
let id = p.new_form("F").unwrap();
|
||||
p.replace_text(id, 0..0, "SUB Form_Load\nERROR 6\nEND SUB\n")
|
||||
.unwrap();
|
||||
let before = p.sources().unwrap();
|
||||
let target = t.0.join("F.frm");
|
||||
p.save_file(id, Some(&Destination::new(&target))).unwrap();
|
||||
let disk = SourceLoader::default().load(&target).unwrap();
|
||||
assert_eq!(
|
||||
before.units[1]
|
||||
.segments
|
||||
.iter()
|
||||
.map(|s| (&s.text, s.first_line))
|
||||
.collect::<Vec<_>>(),
|
||||
disk.units[0]
|
||||
.segments
|
||||
.iter()
|
||||
.map(|s| (&s.text, s.first_line))
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cursor_after_shortening_uses_original_position_and_open_after_save_uses_fresh_disk() {
|
||||
let t = Temp::new();
|
||||
let path = t.write("p.mak", "main.bas\n");
|
||||
t.write("main.bas", "abcdefgh\n");
|
||||
let mut p = Project::open(&path, vec![]).unwrap();
|
||||
let id = p.members()[0];
|
||||
let v = p.open_view(id).unwrap();
|
||||
p.view_mut(v).unwrap().cursor = 9;
|
||||
p.replace_text(id, 1..2, "").unwrap();
|
||||
assert_eq!(p.view(v).unwrap().cursor, 8);
|
||||
p.undo(id).unwrap();
|
||||
assert_eq!(p.view(v).unwrap().cursor, 9);
|
||||
p.replace_text(id, 0..8, "PRINT 7").unwrap();
|
||||
assert!(p
|
||||
.open_project(&path, Decision::Save(&SavePlan::default()))
|
||||
.unwrap());
|
||||
assert_eq!(p.document(p.members()[0]).unwrap().code(), "PRINT 7\n");
|
||||
}
|
||||
Reference in New Issue
Block a user