use super::*;
use crate::{DeclarationErrorKind, test_support::DummyUser};
#[test]
fn the_field_list_holds_nested_fields_in_declaration_order() {
#[derive(Debug, toasty::Model, crate::ModelColumns)]
struct Doc {
#[key]
#[auto]
id: uuid::Uuid,
path: String,
title: String,
}
let schema = Schema::new((
Field::text(Doc::fields().title()),
Section::new("S").schema(Grid::new(2).schema(Group::new().schema((
Field::text(DummyUser::fields().name()),
Field::file(Doc::fields().path()),
)))),
));
let names: Vec<&str> = schema.fields().map(Field::name).collect();
assert_eq!(names, ["title", "name", "path"]);
assert!(
schema.fields().any(|field| field.is_file()),
"a file field nested in a group is in the list"
);
}
#[test]
fn schema_records_duplicate_field_names() {
let errors = Schema::new((
Field::text(DummyUser::fields().name()),
Field::text(DummyUser::fields().name()),
))
.declaration_errors();
assert_eq!(
errors,
[DeclarationErrorKind::DuplicateField {
name: "name".to_string()
}]
);
}
#[tokio::test]
async fn a_misdeclared_schema_fails_to_render() {
let schema = Schema::new((
Field::text(DummyUser::fields().name()),
Field::text(DummyUser::fields().name()),
));
let cx = topcoat::context::CxTestBuilder::new().build();
let values = FormValues::new();
let errors = crate::values::FieldErrors::new();
let Err(error) = schema.render(&cx, Source::form(&values, &errors)).await else {
panic!("a misdeclared schema must not render");
};
assert!(
error.to_string().contains("two fields are named 'name'"),
"a duplicate field must name the field, got {error}"
);
}
#[tokio::test]
async fn a_choice_stores_the_value_its_check_authorised() {
let cx = topcoat::context::CxTestBuilder::new().build();
let schema = Schema::new(
Field::choice(DummyUser::fields().name())
.options(vec!["red".to_string(), "blue".to_string()]),
);
let values = FormValues::from_iter([("name", " red ")]);
assert!(
schema.checked(&cx, &values).await.is_empty(),
"the option check reads the trimmed value"
);
assert_eq!(
crate::values::parse_scalar::<String>("name", &values, None),
Ok("red".to_string()),
"the stored value is the one the check authorised"
);
let invalid = FormValues::from_iter([("name", " re d ")]);
assert!(
!schema.checked(&cx, &invalid).await.is_empty(),
"trimming does not turn a non-option into one"
);
}
#[test]
fn extend_keeps_the_duplicate_field_check() {
let input = || Field::text(DummyUser::fields().name());
let errors = Schema::empty()
.extend(Schema::new(input()))
.extend(Schema::new(input()))
.declaration_errors();
assert_eq!(
errors,
[DeclarationErrorKind::DuplicateField {
name: "name".to_string()
}]
);
}
pub(super) fn tags() -> Schema {
Schema::new(
Field::choice_input("tags")
.options(["rust", "async", "sql"])
.multiple(),
)
}
#[tokio::test]
async fn a_multiple_choice_checks_each_value_it_holds() {
let cx = crate::test_support::cx();
let refused = async |items: Vec<String>| {
let values = FormValues::from_iter([("tags", items)]);
tags().checked(&cx, &values).await.contains_key("tags")
};
let list = |items: &[&str]| items.iter().map(|item| item.to_string()).collect();
assert!(!refused(list(&["rust", "sql"])).await);
assert!(!refused(list(&[])).await, "none chosen");
assert!(
refused(list(&["rust", "go"])).await,
"a value that is no option"
);
assert!(
refused(list(&["rust"; MAX_RELATIONSHIP_OPTIONS + 1])).await,
"more than a choice loads"
);
}
#[tokio::test]
async fn a_multiple_choice_caps_only_the_choices_it_adds() {
let cx = crate::test_support::cx();
let held: Vec<String> = (0..=MAX_RELATIONSHIP_OPTIONS)
.map(|key| format!("rust{key}"))
.collect();
let refused = async |items: Vec<String>| {
let values = FormValues::from_iter([("tags", items)]);
let stored = FormValues::from_iter([("tags", held.clone())]);
let mut errors = crate::FieldErrors::new();
tags()
.check_controls(&cx, &values, &stored, &mut errors)
.await;
errors.contains_key("tags")
};
assert!(
!refused(held.clone()).await,
"a record keeping the choices it holds saves"
);
let mut added = held.clone();
added.push("sql".to_string());
assert!(
!refused(added).await,
"one choice beside them is not past it"
);
let mut many = held.clone();
many.extend((0..=MAX_RELATIONSHIP_OPTIONS).map(|key| format!("sql{key}")));
assert!(
refused(many).await,
"more choices than a choice loads is refused"
);
}
#[test]
fn an_error_no_field_renders_is_unplaced() {
let schema = Schema::new(Field::text(DummyUser::fields().name()));
let unplaced = |key: &str| {
let mut errors = crate::FieldErrors::new();
errors.add(key, "refused");
schema.unplaced_error(&errors).map(str::to_string)
};
assert_eq!(unplaced("name"), None);
assert_eq!(
unplaced("name.0.label").as_deref(),
Some("name.0.label"),
"a text field posts no rows"
);
assert_eq!(unplaced("names").as_deref(), Some("names"));
}
#[test]
fn a_multiple_choices_list_default_fills_the_create_form() {
let schema = Schema::new(
Field::choice_input("tags")
.options(["rust", "async", "sql"])
.multiple()
.default(["rust", "sql"]),
);
assert_eq!(
schema.defaults().get("tags"),
Some(&crate::values::FormValue::List(vec![
"rust".to_string(),
"sql".to_string()
]))
);
}