import ui

This commit is contained in:
Priec
2026-08-18 09:51:50 +02:00
parent eb3e22c45e
commit a8877e4418
9 changed files with 309 additions and 107 deletions

View File

@@ -109,14 +109,6 @@ pub(crate) async fn prepare_step(
.collect::<HashMap<_, _>>();
let has_answers = !form.destination.is_empty();
let positions_by_name = source
.header
.iter()
.enumerate()
.fold(HashMap::<&str, Vec<usize>>::new(), |mut positions, (index, name)| {
positions.entry(name.as_str()).or_default().push(index);
positions
});
let rows = destination
.columns
.iter()
@@ -125,10 +117,7 @@ pub(crate) async fn prepare_step(
let chosen_index = if has_answers {
answered.get(&key).copied()
} else {
positions_by_name
.get(column.name.as_str())
.filter(|positions| positions.len() == 1)
.and_then(|positions| positions.first().copied())
None
};
MappingRow {
key,
@@ -148,8 +137,8 @@ pub(crate) async fn prepare_step(
let step = Step::Mapping(MappingStep {
table_name: destination.table_name.clone(),
rows,
sources: source_options(locale, &source),
rows,
source_rows: source.rows.len(),
mapped,
attention,

View File

@@ -169,11 +169,15 @@ pub(crate) enum Step {
pub(crate) struct MappingStep {
pub table_name: String,
/// One fixed row per destination table column.
pub rows: Vec<MappingRow>,
/// Columns parsed from the CSV's first row, available to connect to a
/// destination by dragging or selecting.
pub sources: Vec<SourceOption>,
/// One fixed row per destination table column.
///
/// Kept as its own list rather than zipped with `sources`: the two sides
/// have no row-for-row relationship, and showing them as if they did makes
/// mere adjacency look like a mapping.
pub rows: Vec<MappingRow>,
pub source_rows: usize,
pub mapped: usize,
pub attention: usize,

View File

@@ -223,6 +223,31 @@ mod tests {
assert!(html.contains(">Not mapped — leave empty</option>"), "{html}");
}
/// The two sides are two lists, not one zipped table: a source chip carries
/// only its own position, and a destination row only its own key. Nothing
/// in the markup pairs the two by their displayed order.
#[test]
fn the_mapping_step_keeps_the_two_sides_apart() {
let html = render_step(&page(mapping()));
assert_eq!(html.matches(r#"class="source-chip""#).count(), 2);
assert_eq!(html.matches("data-map-target").count(), 2);
// A destination names itself, so the palette can say where a column went.
assert!(html.contains(r#"data-destination-name="a""#), "{html}");
}
/// Moving a batch is the point of the step, so both ways of doing it are on
/// the page: the whole file at once, and a selection placed by hand.
#[test]
fn the_mapping_step_offers_the_batch_actions() {
let html = render_step(&page(mapping()));
assert!(html.contains("data-map-in-order"), "{html}");
assert!(html.contains("data-clear-all-mappings"), "{html}");
assert!(html.contains("data-select-all-sources"), "{html}");
assert!(html.contains(r#"draggable="true""#), "{html}");
}
/// The destination travels as its stable identity, never as its name, so a
/// rename between the mapping and the import moves with the column.
#[test]