Merge commit '44718e8a' into feat/8jk6.3-date-hierarchy
# Conflicts: # crates/improvise-io/src/import/wizard.rs # tests/headless_cli.rs
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
//! A folder import's per-file views, saved and reloaded, as both front ends
|
||||
//! project them (improvise-lpzu). Evidence: `context/test-evidence/cli.md`.
|
||||
|
||||
use improvise::import::csv_parser::merge_csvs_from;
|
||||
use improvise::import::wizard::ImportPipeline;
|
||||
use improvise::persistence::{format_md, parse_md};
|
||||
use improvise::ui::app::App;
|
||||
use improvise_command::ui::project::{Viewport, grid_in};
|
||||
use improvise_io::storage::{MemoryFs, Storage};
|
||||
|
||||
fn reloaded_folder_import() -> App {
|
||||
let fs = MemoryFs::new();
|
||||
let files = [
|
||||
(
|
||||
"maat/age.csv",
|
||||
"entity,age-months\nsrc/a.rs,2\nsrc/c.rs,7\n",
|
||||
),
|
||||
(
|
||||
"maat/coupling.csv",
|
||||
"entity,coupled,degree,average-revs\nsrc/a.rs,src/b.rs,80,5\nsrc/b.rs,src/c.rs,40,3\n",
|
||||
),
|
||||
("maat/soc.csv", "entity,soc\n"),
|
||||
];
|
||||
for (path, body) in files {
|
||||
fs.write(std::path::Path::new(path), body.as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
let merged = merge_csvs_from(&fs, &files.map(|(p, _)| p)).unwrap();
|
||||
let (wb, _) = ImportPipeline::from_merge(merged)
|
||||
.build_model_with_warnings()
|
||||
.unwrap();
|
||||
App::new(parse_md(&format_md(&wb)).unwrap(), None)
|
||||
}
|
||||
|
||||
/// Evidence: `context/test-evidence/cli.md`.
|
||||
#[test]
|
||||
fn a_saved_per_file_view_projects_only_its_file() {
|
||||
let app = reloaded_folder_import();
|
||||
let whole = Viewport {
|
||||
row_offset: 0,
|
||||
col_offset: 0,
|
||||
rows: 100,
|
||||
cols: 100,
|
||||
};
|
||||
for (view, texts) in [("age", ["2.00", "7.00"]), ("coupling", ["5.00", "3.00"])] {
|
||||
let grid = grid_in(&app, view, whole);
|
||||
let file = grid.page_coords.iter().find(|p| p.cat == "File").unwrap();
|
||||
assert_eq!((file.selected.as_str(), &file.items), (view, &None));
|
||||
let drawn: Vec<&str> = grid
|
||||
.cells
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|c| c.text.as_str())
|
||||
.filter(|t| !t.is_empty())
|
||||
.collect();
|
||||
assert_eq!(drawn, texts, "{view}");
|
||||
}
|
||||
|
||||
// A header-only file: its item exists, and its view has nothing editable.
|
||||
let soc = grid_in(&app, "soc", whole);
|
||||
let file = soc.page_coords.iter().find(|p| p.cat == "File").unwrap();
|
||||
assert_eq!(file.selected, "soc");
|
||||
assert!(soc.records, "a file with no measure opens in records mode");
|
||||
assert_eq!(soc.extent.rows, 0, "{soc:?}");
|
||||
}
|
||||
@@ -665,3 +665,130 @@ fn a_headless_date_import_splits_when_told_to() {
|
||||
assert!(doc.contains("## Category: Date_Month"), "{doc}");
|
||||
assert!(doc.contains("2024-01-15"), "the raw dates stay: {doc}");
|
||||
}
|
||||
|
||||
/// code-maat's reports: three schemas, a stray note, and room for the output.
|
||||
fn code_maat_folder(scratch: &Scratch) -> PathBuf {
|
||||
let dir = scratch.path("maat");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
for (name, body) in [
|
||||
(
|
||||
"coupling.csv",
|
||||
"entity,coupled,degree,average-revs\nsrc/a.rs,src/b.rs,80,5\n",
|
||||
),
|
||||
("age.csv", "entity,age-months\nsrc/a.rs,2\nsrc/b.rs,9\n"),
|
||||
(
|
||||
"abs-churn.csv",
|
||||
"date,added,deleted,commits\n2026-01-02,10,3,2\n",
|
||||
),
|
||||
("README.txt", "not a report\n"),
|
||||
] {
|
||||
std::fs::write(dir.join(name), body).unwrap();
|
||||
}
|
||||
dir
|
||||
}
|
||||
|
||||
/// improvise-lpzu: a folder argument is its `*.csv` files, merged sparsely and
|
||||
/// in name order. Evidence: `context/test-evidence/cli.md`.
|
||||
#[test]
|
||||
fn a_headless_import_of_a_folder_merges_its_csv_files_and_repeats_byte_for_byte() {
|
||||
let scratch = Scratch::new("folder_import");
|
||||
let dir = code_maat_folder(&scratch);
|
||||
let out_path = dir.join("maat.improv");
|
||||
|
||||
let first = run_import(&dir, &out_path, &[]);
|
||||
assert!(first.status.success(), "{}", stderr_of(&first));
|
||||
let doc = std::fs::read_to_string(&out_path).unwrap();
|
||||
for line in [
|
||||
"File=coupling, _Measure=degree, coupled=|src/b.rs|, entity=|src/a.rs| = 80",
|
||||
"File=age, _Measure=age-months, entity=|src/b.rs| = 9",
|
||||
"File=abs-churn, _Measure=added, date=|2026-01-02| = 10",
|
||||
] {
|
||||
assert!(doc.lines().any(|l| l == line), "{line} missing from\n{doc}");
|
||||
}
|
||||
assert!(!doc.contains("README"), "{doc}");
|
||||
let views: Vec<&str> = doc.lines().filter(|l| l.starts_with("## View: ")).collect();
|
||||
assert_eq!(
|
||||
views,
|
||||
[
|
||||
"## View: Default",
|
||||
"## View: abs-churn",
|
||||
"## View: age",
|
||||
"## View: coupling"
|
||||
]
|
||||
);
|
||||
assert!(doc.lines().any(|l| l == "File: filter, coupling"), "{doc}");
|
||||
assert!(doc.contains("Initial View: Default"), "{doc}");
|
||||
|
||||
// The output now sits in the folder; a second run must not read it.
|
||||
let second = run_import(&dir, &out_path, &[]);
|
||||
assert!(second.status.success(), "{}", stderr_of(&second));
|
||||
assert_eq!(std::fs::read_to_string(&out_path).unwrap(), doc);
|
||||
}
|
||||
|
||||
/// improvise-lpzu: a file and a folder together are one merge, and a folder
|
||||
/// with nothing to import is named. Evidence: `context/test-evidence/cli.md`.
|
||||
#[test]
|
||||
fn a_headless_import_takes_files_and_folders_together_and_refuses_an_empty_folder() {
|
||||
let scratch = Scratch::new("folder_mixed");
|
||||
let dir = code_maat_folder(&scratch);
|
||||
let extra = scratch.path("extra.csv");
|
||||
std::fs::write(&extra, "entity,age-months\nsrc/z.rs,4\n").unwrap();
|
||||
let out_path = scratch.path("out.improv");
|
||||
|
||||
let out = Command::new(bin())
|
||||
.args(["import", "--no-wizard", "-o"])
|
||||
.arg(&out_path)
|
||||
.arg(&extra)
|
||||
.arg(&dir)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(out.status.success(), "{}", stderr_of(&out));
|
||||
let doc = std::fs::read_to_string(&out_path).unwrap();
|
||||
assert!(
|
||||
doc.contains("File=extra, _Measure=age-months, entity=|src/z.rs| = 4"),
|
||||
"{doc}"
|
||||
);
|
||||
assert!(doc.contains("File=coupling"), "{doc}");
|
||||
|
||||
let empty = scratch.path("empty");
|
||||
std::fs::create_dir_all(&empty).unwrap();
|
||||
let refused = run_import(&empty, &scratch.path("never.improv"), &[]);
|
||||
assert!(!refused.status.success());
|
||||
let err = stderr_of(&refused);
|
||||
assert!(
|
||||
err.contains(&format!("no CSV files in folder {}", empty.display())),
|
||||
"{err}"
|
||||
);
|
||||
assert!(!scratch.path("never.improv").exists());
|
||||
}
|
||||
|
||||
/// improvise-lpzu, the user's ruling: a folder always merges, so even one CSV
|
||||
/// keeps its file identity. Evidence: `context/test-evidence/cli.md`.
|
||||
#[test]
|
||||
fn a_folder_of_one_csv_still_gets_a_file_category_and_its_view_but_a_lone_file_does_not() {
|
||||
let scratch = Scratch::new("folder_single");
|
||||
let dir = scratch.path("one");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let csv = dir.join("age.csv");
|
||||
std::fs::write(&csv, "entity,age-months\nsrc/a.rs,2\n").unwrap();
|
||||
|
||||
let from_folder = scratch.path("folder.improv");
|
||||
let out = run_import(&dir, &from_folder, &[]);
|
||||
assert!(out.status.success(), "{}", stderr_of(&out));
|
||||
let doc = std::fs::read_to_string(&from_folder).unwrap();
|
||||
assert!(doc.contains("## Category: File\n- age\n"), "{doc}");
|
||||
assert!(doc.lines().any(|l| l == "File: filter, age"), "{doc}");
|
||||
assert!(
|
||||
doc.contains("File=age, _Measure=age-months, entity=|src/a.rs| = 2"),
|
||||
"{doc}"
|
||||
);
|
||||
|
||||
let from_file = scratch.path("file.improv");
|
||||
let out = run_import(&csv, &from_file, &[]);
|
||||
assert!(out.status.success(), "{}", stderr_of(&out));
|
||||
let doc = std::fs::read_to_string(&from_file).unwrap();
|
||||
assert!(
|
||||
!doc.contains("File"),
|
||||
"a lone file keeps today's shape: {doc}"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user