move-Command Tests, Refactor, Fix (#11904)

fixes #11783 

# Description
Firstly Tests for the `move` Command have been added. Afterwards some
duplicate Code has been removed and finally an error-message has been
added for when a column is tried to be moved based on itself. This
should fix #11783 .

To reiterate, the example of the initial issue now plays out as follows:
```shell
> {a: 1} | move a --after a                             
Error: nu:🐚:incompatible_parameters

  × Incompatible parameters.
   ╭─[entry #1:1:15]
 1 │ {a: 1} | move a --after a
   ·               ┬         ┬
   ·               │         ╰── relative to itself
   ·               ╰── Column cannot be moved
   ╰────
``` 

# User-Facing Changes
The error message shown above.

# Tests + Formatting
I added some Tests for the behavior of the command. If I should add
more, please let me know but I added everything that came to mind when
thinking about the command.

---------

Co-authored-by: dannou812 <dannou281@gmail.com>
This commit is contained in:
dannou812 2024-02-20 14:23:46 +01:00 committed by GitHub
parent 63ccc62a24
commit 7c1eb6b0c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -194,10 +194,13 @@ fn move_record_columns(
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
let mut column_idx: Vec<usize> = Vec::with_capacity(columns.len()); let mut column_idx: Vec<usize> = Vec::with_capacity(columns.len());
// Check if before/after column exist let pivot = match &before_or_after.item {
match &before_or_after.item { BeforeOrAfter::Before(before) => before,
BeforeOrAfter::After(after) => { BeforeOrAfter::After(after) => after,
if !record.contains(after) { };
// check if pivot exists
if !record.contains(pivot) {
return Err(ShellError::GenericError { return Err(ShellError::GenericError {
error: "Cannot move columns".into(), error: "Cannot move columns".into(),
msg: "column does not exist".into(), msg: "column does not exist".into(),
@ -206,23 +209,10 @@ fn move_record_columns(
inner: vec![], inner: vec![],
}); });
} }
}
BeforeOrAfter::Before(before) => {
if !record.contains(before) {
return Err(ShellError::GenericError {
error: "Cannot move columns".into(),
msg: "column does not exist".into(),
span: Some(before_or_after.span),
help: None,
inner: vec![],
});
}
}
}
// Find indices of columns to be moved // Find indices of columns to be moved
for column in columns.iter() { for column in columns.iter() {
if let Some(idx) = record.index_of(&column.coerce_str()?) { if let Some(idx) = record.index_of(column.coerce_string()?) {
column_idx.push(idx); column_idx.push(idx);
} else { } else {
return Err(ShellError::GenericError { return Err(ShellError::GenericError {
@ -233,14 +223,26 @@ fn move_record_columns(
inner: vec![], inner: vec![],
}); });
} }
let column_as_string = column.coerce_string()?;
// check if column is also pivot
if &column_as_string == pivot {
return Err(ShellError::IncompatibleParameters {
left_message: "Column cannot be moved".to_string(),
left_span: column.span(),
right_message: "relative to itself".to_string(),
right_span: before_or_after.span,
});
}
} }
let mut out = Record::with_capacity(record.len()); let mut out = Record::with_capacity(record.len());
for (i, (inp_col, inp_val)) in record.iter().enumerate() { for (i, (inp_col, inp_val)) in record.iter().enumerate() {
match &before_or_after.item { if inp_col == pivot {
BeforeOrAfter::After(after) if after == inp_col => { if matches!(&before_or_after.item, BeforeOrAfter::After(..)) {
out.push(inp_col.clone(), inp_val.clone()); out.push(inp_col.clone(), inp_val.clone());
}
for idx in column_idx.iter() { for idx in column_idx.iter() {
if let Some((col, val)) = record.get_index(*idx) { if let Some((col, val)) = record.get_index(*idx) {
@ -253,29 +255,14 @@ fn move_record_columns(
}); });
} }
} }
}
BeforeOrAfter::Before(before) if before == inp_col => {
for idx in column_idx.iter() {
if let Some((col, val)) = record.get_index(*idx) {
out.push(col.clone(), val.clone());
} else {
return Err(ShellError::NushellFailedSpanned {
msg: "Error indexing input columns".to_string(),
label: "originates from here".to_string(),
span,
});
}
}
if matches!(&before_or_after.item, BeforeOrAfter::Before(..)) {
out.push(inp_col.clone(), inp_val.clone()); out.push(inp_col.clone(), inp_val.clone());
} }
_ => { } else if !column_idx.contains(&i) {
if !column_idx.contains(&i) {
out.push(inp_col.clone(), inp_val.clone()); out.push(inp_col.clone(), inp_val.clone());
} }
} }
}
}
Ok(Value::record(out, span)) Ok(Value::record(out, span))
} }
@ -284,10 +271,194 @@ fn move_record_columns(
mod test { mod test {
use super::*; use super::*;
// helper
fn get_test_record(columns: Vec<&str>, values: Vec<i64>) -> Record {
let test_span = Span::test_data();
Record::from_raw_cols_vals(
columns.iter().map(|col| col.to_string()).collect(),
values.iter().map(|val| Value::test_int(*val)).collect(),
test_span,
test_span,
)
.unwrap()
}
#[test] #[test]
fn test_examples() { fn test_examples() {
use crate::test_examples; use crate::test_examples;
test_examples(Move {}) test_examples(Move {})
} }
#[test]
fn move_after_with_single_column() {
let test_span = Span::test_data();
let test_record = get_test_record(vec!["a", "b", "c", "d"], vec![1, 2, 3, 4]);
let after: Spanned<BeforeOrAfter> = Spanned {
item: BeforeOrAfter::After("c".to_string()),
span: test_span,
};
let columns = [Value::test_string("a")];
// corresponds to: {a: 1, b: 2, c: 3, d: 4} | move a --after c
let result = move_record_columns(&test_record, &columns, &after, test_span);
assert!(result.is_ok());
let result_rec_tmp = result.unwrap();
let result_record = result_rec_tmp.as_record().unwrap();
assert_eq!(*result_record.get_index(0).unwrap().0, "b".to_string());
assert_eq!(*result_record.get_index(1).unwrap().0, "c".to_string());
assert_eq!(*result_record.get_index(2).unwrap().0, "a".to_string());
assert_eq!(*result_record.get_index(3).unwrap().0, "d".to_string());
}
#[test]
fn move_after_with_multiple_columns() {
let test_span = Span::test_data();
let test_record = get_test_record(vec!["a", "b", "c", "d", "e"], vec![1, 2, 3, 4, 5]);
let after: Spanned<BeforeOrAfter> = Spanned {
item: BeforeOrAfter::After("c".to_string()),
span: test_span,
};
let columns = [Value::test_string("b"), Value::test_string("e")];
// corresponds to: {a: 1, b: 2, c: 3, d: 4, e: 5} | move b e --after c
let result = move_record_columns(&test_record, &columns, &after, test_span);
assert!(result.is_ok());
let result_rec_tmp = result.unwrap();
let result_record = result_rec_tmp.as_record().unwrap();
assert_eq!(*result_record.get_index(0).unwrap().0, "a".to_string());
assert_eq!(*result_record.get_index(1).unwrap().0, "c".to_string());
assert_eq!(*result_record.get_index(2).unwrap().0, "b".to_string());
assert_eq!(*result_record.get_index(3).unwrap().0, "e".to_string());
assert_eq!(*result_record.get_index(4).unwrap().0, "d".to_string());
}
#[test]
fn move_before_with_single_column() {
let test_span = Span::test_data();
let test_record = get_test_record(vec!["a", "b", "c", "d"], vec![1, 2, 3, 4]);
let after: Spanned<BeforeOrAfter> = Spanned {
item: BeforeOrAfter::Before("b".to_string()),
span: test_span,
};
let columns = [Value::test_string("d")];
// corresponds to: {a: 1, b: 2, c: 3, d: 4} | move d --before b
let result = move_record_columns(&test_record, &columns, &after, test_span);
assert!(result.is_ok());
let result_rec_tmp = result.unwrap();
let result_record = result_rec_tmp.as_record().unwrap();
assert_eq!(*result_record.get_index(0).unwrap().0, "a".to_string());
assert_eq!(*result_record.get_index(1).unwrap().0, "d".to_string());
assert_eq!(*result_record.get_index(2).unwrap().0, "b".to_string());
assert_eq!(*result_record.get_index(3).unwrap().0, "c".to_string());
}
#[test]
fn move_before_with_multiple_columns() {
let test_span = Span::test_data();
let test_record = get_test_record(vec!["a", "b", "c", "d", "e"], vec![1, 2, 3, 4, 5]);
let after: Spanned<BeforeOrAfter> = Spanned {
item: BeforeOrAfter::Before("b".to_string()),
span: test_span,
};
let columns = [Value::test_string("c"), Value::test_string("e")];
// corresponds to: {a: 1, b: 2, c: 3, d: 4, e: 5} | move c e --before b
let result = move_record_columns(&test_record, &columns, &after, test_span);
assert!(result.is_ok());
let result_rec_tmp = result.unwrap();
let result_record = result_rec_tmp.as_record().unwrap();
assert_eq!(*result_record.get_index(0).unwrap().0, "a".to_string());
assert_eq!(*result_record.get_index(1).unwrap().0, "c".to_string());
assert_eq!(*result_record.get_index(2).unwrap().0, "e".to_string());
assert_eq!(*result_record.get_index(3).unwrap().0, "b".to_string());
assert_eq!(*result_record.get_index(4).unwrap().0, "d".to_string());
}
#[test]
fn move_with_multiple_columns_reorders_columns() {
let test_span = Span::test_data();
let test_record = get_test_record(vec!["a", "b", "c", "d", "e"], vec![1, 2, 3, 4, 5]);
let after: Spanned<BeforeOrAfter> = Spanned {
item: BeforeOrAfter::After("e".to_string()),
span: test_span,
};
let columns = [
Value::test_string("d"),
Value::test_string("c"),
Value::test_string("a"),
];
// corresponds to: {a: 1, b: 2, c: 3, d: 4, e: 5} | move d c a --after e
let result = move_record_columns(&test_record, &columns, &after, test_span);
assert!(result.is_ok());
let result_rec_tmp = result.unwrap();
let result_record = result_rec_tmp.as_record().unwrap();
assert_eq!(*result_record.get_index(0).unwrap().0, "b".to_string());
assert_eq!(*result_record.get_index(1).unwrap().0, "e".to_string());
assert_eq!(*result_record.get_index(2).unwrap().0, "d".to_string());
assert_eq!(*result_record.get_index(3).unwrap().0, "c".to_string());
assert_eq!(*result_record.get_index(4).unwrap().0, "a".to_string());
}
#[test]
fn move_fails_when_pivot_not_present() {
let test_span = Span::test_data();
let test_record = get_test_record(vec!["a", "b"], vec![1, 2]);
let after: Spanned<BeforeOrAfter> = Spanned {
item: BeforeOrAfter::Before("non-existent".to_string()),
span: test_span,
};
let columns = [Value::test_string("a")];
let result = move_record_columns(&test_record, &columns, &after, test_span);
assert!(result.is_err());
}
#[test]
fn move_fails_when_column_not_present() {
let test_span = Span::test_data();
let test_record = get_test_record(vec!["a", "b"], vec![1, 2]);
let after: Spanned<BeforeOrAfter> = Spanned {
item: BeforeOrAfter::Before("b".to_string()),
span: test_span,
};
let columns = [Value::test_string("a"), Value::test_string("non-existent")];
let result = move_record_columns(&test_record, &columns, &after, test_span);
assert!(result.is_err());
}
#[test]
fn move_fails_when_column_is_also_pivot() {
let test_span = Span::test_data();
let test_record = get_test_record(vec!["a", "b", "c", "d"], vec![1, 2, 3, 4]);
let after: Spanned<BeforeOrAfter> = Spanned {
item: BeforeOrAfter::After("b".to_string()),
span: test_span,
};
let columns = [Value::test_string("b"), Value::test_string("d")];
let result = move_record_columns(&test_record, &columns, &after, test_span);
assert!(result.is_err());
}
} }