8bd4b1a77b
- 8jk6.4: the CSV parser yields source text (CsvRecords), and only the role reads it as a number. A forced category keeps00565796and220944e2verbatim; measures and analysis read the text through csv_parser::number_in. CsvRecords reaches a pipeline only through ImportPipeline::from_csv, so handing CSV text to the JSON constructor no longer compiles. - 8jk6.5: an import formula that does not parse is refused by name with its parse error: the build refuses (headless exits non-zero and writes nothing), and the wizard keeps it out of the list with the text still in the buffer. - 8jk6.6: an import that kept no record refuses and says why, from the build and from the wizard's review step. A partial skip stays a warning with a count and exit 0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
68 lines
2.4 KiB
Rust
68 lines
2.4 KiB
Rust
//! End-to-end check that a real imported model renders records mode in wide
|
|
//! format: CSV → import → save → load → records grid (improvise-c5v).
|
|
|
|
use improvise::import::wizard::ImportPipeline;
|
|
use improvise::persistence;
|
|
use improvise::view::{Axis, GridLayout};
|
|
|
|
/// Import a two-record CSV, round-trip it through `.improv`, and read the
|
|
/// records grid off the loaded model.
|
|
#[test]
|
|
fn imported_model_shows_one_row_per_record_with_measure_columns() {
|
|
let dir = tempfile::tempdir().expect("tempdir");
|
|
let csv = dir.path().join("w.csv");
|
|
std::fs::write(
|
|
&csv,
|
|
"region,product,revenue,cost\nEast,Shirts,100,40\nWest,Pants,200,80\n",
|
|
)
|
|
.expect("write csv");
|
|
|
|
let records = improvise::import::csv_parser::parse_csv(&csv).expect("csv parses");
|
|
let pipeline = ImportPipeline::from_csv(records);
|
|
let workbook = pipeline.build_model().expect("model builds");
|
|
|
|
let saved = dir.path().join("w.improv");
|
|
persistence::save(&workbook, &saved).expect("saves");
|
|
let mut wb = persistence::load(&saved).expect("loads");
|
|
|
|
let v = wb.active_view_mut();
|
|
v.set_axis("_Index", Axis::Row);
|
|
v.set_axis("_Dim", Axis::Column);
|
|
for cat in ["region", "product", "_Measure"] {
|
|
v.set_axis(cat, Axis::None);
|
|
}
|
|
let layout = GridLayout::new(&wb.model, wb.active_view());
|
|
assert!(layout.is_records_mode());
|
|
|
|
let cols: Vec<String> = (0..layout.col_count())
|
|
.map(|i| layout.col_label(i))
|
|
.collect();
|
|
assert_eq!(
|
|
cols,
|
|
vec!["product", "region", "+cat", "cost", "revenue", "+"],
|
|
"categories, the new-category column, one column per measure, then \
|
|
the new-measure column — each `+` sits next to what it adds to \
|
|
(improvise-2zq)"
|
|
);
|
|
assert_eq!(layout.row_count(), 2, "two source records, not four cells");
|
|
|
|
// Row order follows the store; the contract under test is that each
|
|
// source record is one row carrying both its measures side by side.
|
|
let mut rows: Vec<Vec<String>> = (0..layout.row_count())
|
|
.map(|r| {
|
|
(0..layout.col_count())
|
|
.map(|c| layout.records_display(r, c).unwrap())
|
|
.collect()
|
|
})
|
|
.collect();
|
|
rows.sort();
|
|
assert_eq!(
|
|
rows,
|
|
vec![
|
|
vec!["Pants", "West", "", "80", "200", ""],
|
|
vec!["Shirts", "East", "", "40", "100", ""],
|
|
],
|
|
"got {rows:?}"
|
|
);
|
|
}
|