mod common;
use common::*;
use tempfile::tempdir;
const QUIRKY: &str = "# my project manifest — hands off the comments\n\
[package]\n\
name = \"quirky\" # extra spaces are intentional\n\
version = \"0.1.0\"\n\
entry = \"src/main.lg\"\n\
\n\
# dependencies below\n\
[dependencies]\n\
existing = \"1.0\" # keep me\n";
fn scaffold_quirky(dir: &std::path::Path) {
std::fs::create_dir_all(dir.join("src")).unwrap();
std::fs::write(dir.join("Largo.toml"), QUIRKY).unwrap();
std::fs::write(dir.join("src/main.lg"), "## Main\n Show 1.\n").unwrap();
}
fn manifest_text(dir: &std::path::Path) -> String {
std::fs::read_to_string(dir.join("Largo.toml")).unwrap()
}
#[test]
fn add_version_dep_preserves_formatting() {
let dir = tempdir().unwrap();
scaffold_quirky(dir.path());
let out = largo_in(dir.path(), &["add", "foo@1.2"]);
assert_eq!(out.status.code(), Some(0), "add: {}", stderr(&out));
let text = manifest_text(dir.path());
assert!(text.contains("foo = \"1.2\""), "dep must be inserted:\n{text}");
for preserved in [
"# my project manifest — hands off the comments",
"name = \"quirky\" # extra spaces are intentional",
"# dependencies below",
"existing = \"1.0\" # keep me",
] {
assert!(text.contains(preserved), "must preserve {preserved:?}:\n{text}");
}
let manifest: logicaffeine_cli::project::manifest::Manifest =
toml::from_str(&text).expect("edited manifest must stay valid");
assert!(manifest.dependencies.contains_key("foo"));
}
#[test]
fn add_without_version_is_wildcard() {
let dir = tempdir().unwrap();
scaffold_quirky(dir.path());
let out = largo_in(dir.path(), &["add", "bar"]);
assert_eq!(out.status.code(), Some(0), "{}", stderr(&out));
assert!(manifest_text(dir.path()).contains("bar = \"*\""));
}
#[test]
fn add_path_dependency() {
let dir = tempdir().unwrap();
scaffold_quirky(dir.path());
let out = largo_in(dir.path(), &["add", "math", "--path", "./math"]);
assert_eq!(out.status.code(), Some(0), "{}", stderr(&out));
let manifest: logicaffeine_cli::project::manifest::Manifest =
toml::from_str(&manifest_text(dir.path())).unwrap();
match manifest.dependencies.get("math") {
Some(logicaffeine_cli::project::manifest::DependencySpec::Detailed(detail)) => {
assert_eq!(detail.path.as_deref(), Some("./math"));
}
other => panic!("expected a detailed path dep, got {other:?}"),
}
}
#[test]
fn add_git_dependency() {
let dir = tempdir().unwrap();
scaffold_quirky(dir.path());
let out = largo_in(
dir.path(),
&["add", "remote", "--git", "https://example.com/remote.git"],
);
assert_eq!(out.status.code(), Some(0), "{}", stderr(&out));
assert!(manifest_text(dir.path()).contains("https://example.com/remote.git"));
}
#[test]
fn add_logos_uri_dependency() {
let dir = tempdir().unwrap();
scaffold_quirky(dir.path());
let out = largo_in(dir.path(), &["add", "logos:std"]);
assert_eq!(out.status.code(), Some(0), "{}", stderr(&out));
assert!(manifest_text(dir.path()).contains("std = \"logos:std\""));
}
#[test]
fn re_add_updates_in_place() {
let dir = tempdir().unwrap();
scaffold_quirky(dir.path());
assert_eq!(largo_in(dir.path(), &["add", "foo@1.2"]).status.code(), Some(0));
assert_eq!(largo_in(dir.path(), &["add", "foo@2.0"]).status.code(), Some(0));
let text = manifest_text(dir.path());
assert!(text.contains("foo = \"2.0\""), "must update:\n{text}");
assert!(!text.contains("foo = \"1.2\""), "must not duplicate:\n{text}");
assert_eq!(text.matches("foo = ").count(), 1);
}
#[test]
fn remove_deletes_only_that_dep() {
let dir = tempdir().unwrap();
scaffold_quirky(dir.path());
assert_eq!(largo_in(dir.path(), &["add", "doomed@1"]).status.code(), Some(0));
let out = largo_in(dir.path(), &["remove", "doomed"]);
assert_eq!(out.status.code(), Some(0), "remove: {}", stderr(&out));
let text = manifest_text(dir.path());
assert!(!text.contains("doomed"), "removed dep must be gone:\n{text}");
assert!(text.contains("existing = \"1.0\" # keep me"), "neighbor survives:\n{text}");
}
#[test]
fn remove_absent_dep_fails() {
let dir = tempdir().unwrap();
scaffold_quirky(dir.path());
let out = largo_in(dir.path(), &["remove", "phantom"]);
assert_eq!(out.status.code(), Some(1));
assert!(strip_ansi(&stderr(&out)).contains("phantom"));
}
#[test]
fn path_and_git_conflict() {
let dir = tempdir().unwrap();
scaffold_quirky(dir.path());
let out = largo_in(dir.path(), &["add", "x", "--path", "./x", "--git", "url"]);
assert_eq!(out.status.code(), Some(2));
}
#[test]
fn add_outside_project_fails() {
let dir = tempdir().unwrap();
let out = largo_in(dir.path(), &["add", "foo"]);
assert_eq!(out.status.code(), Some(1));
}
#[test]
fn inline_table_dependencies_are_editable() {
let dir = tempdir().unwrap();
std::fs::create_dir_all(dir.path().join("src")).unwrap();
std::fs::write(
dir.path().join("Largo.toml"),
"dependencies = { existing = \"1.0\" }\n\n[package]\nname = \"inline\"\nversion = \"0.1.0\"\n",
)
.unwrap();
std::fs::write(dir.path().join("src/main.lg"), "## Main\n Show 1.\n").unwrap();
let add = largo_in(dir.path(), &["add", "foo@2.0"]);
assert_eq!(add.status.code(), Some(0), "add to inline table: {}", stderr(&add));
assert!(manifest_text(dir.path()).contains("foo"), "{}", manifest_text(dir.path()));
let rm = largo_in(dir.path(), &["remove", "existing"]);
assert_eq!(rm.status.code(), Some(0), "remove from inline table: {}", stderr(&rm));
assert!(!manifest_text(dir.path()).contains("existing"));
}
#[test]
fn add_creates_dependencies_section() {
let dir = tempdir().unwrap();
std::fs::create_dir_all(dir.path().join("src")).unwrap();
std::fs::write(
dir.path().join("Largo.toml"),
"[package]\nname = \"bare\"\nversion = \"0.1.0\"\n",
)
.unwrap();
std::fs::write(dir.path().join("src/main.lg"), "## Main\n Show 1.\n").unwrap();
let out = largo_in(dir.path(), &["add", "foo@1.0"]);
assert_eq!(out.status.code(), Some(0), "{}", stderr(&out));
let text = manifest_text(dir.path());
assert!(text.contains("[dependencies]"));
assert!(text.contains("foo = \"1.0\""));
}