use super::*;
use crate::{
form::{FieldErrors, FormValues},
schema::{Group, Section, Source},
test_support::{Html, cx},
};
#[derive(Debug, toasty::Model)]
struct Customer {
#[key]
#[auto]
id: uuid::Uuid,
kind: String,
vat: Option<String>,
company: Option<String>,
note: Option<String>,
active: bool,
}
fn kind() -> ChoiceField {
Field::choice(Customer::fields().kind()).options(["person", "company"])
}
fn values(kind: &str) -> FormValues {
FormValues::from_iter([("kind".to_string(), kind.to_string())])
}
#[test]
fn a_failing_condition_hides_the_fields_it_guards() {
let kind = kind();
let vat = Field::text(Customer::fields().vat()).visible_when(&kind, ["company"]);
let block = Group::new()
.schema(Field::text(Customer::fields().company()))
.visible_when(&kind, ["company", "partner"]);
let schema = Schema::new((kind, vat, block, Field::text(Customer::fields().note())));
let mut hidden = schema.condition_hidden(&values("person"));
hidden.sort();
assert_eq!(hidden, ["company", "vat"]);
assert!(schema.condition_hidden(&values("company")).is_empty());
assert_eq!(
schema.condition_hidden(&values("partner")),
["vat"],
"a block shows for any of its values"
);
assert_eq!(
schema.hidden_fields(&FormValues::new()).len(),
2,
"a submission without the watched field hides what it guards"
);
}
#[tokio::test]
async fn a_hidden_field_renders_in_a_disabled_fieldset() {
let cx = cx();
let render = |kind_value: &'static str| {
let cx = &cx;
async move {
let kind = kind();
let vat = Field::text(Customer::fields().vat()).visible_when(&kind, ["company"]);
let values = values(kind_value);
let errors = FieldErrors::new();
Schema::new((kind, vat))
.render(cx, Source::form(&values, &errors))
.await
.html(cx)
.await
}
};
for (kind, disabled) in [("person", true), ("company", false)] {
let html = render(kind).await;
let input = html.find("name=\"vat\"").expect("the vat input");
let fieldset = &html[html[..input].rfind("<fieldset").expect("its fieldset")..input];
let tag = &fieldset[..fieldset.find('>').expect("a closed tag")];
assert_eq!(
tag.contains(" disabled"),
disabled,
"kind={kind}: the fieldset must be disabled exactly when hidden, got {tag}"
);
assert_eq!(tag.contains(" hidden"), disabled, "kind={kind}, got {tag}");
let select = html.find("<select").expect("the kind select");
assert!(
html[..select].contains("data-topcoat-on:change"),
"the watched field renders inside its change handler, got {html}"
);
}
}
#[tokio::test]
async fn forms_sharing_a_page_keep_their_ids_and_conditions_apart() {
let cx = cx();
let render = |scope: &'static str, kind_value: &'static str| {
let cx = &cx;
async move {
let kind = kind();
let vat = Field::text(Customer::fields().vat()).visible_when(&kind, ["company"]);
let values = values(kind_value);
let errors = FieldErrors::new();
Schema::new((kind, vat))
.render(cx, Source::form(&values, &errors).scoped(scope))
.await
.html(cx)
.await
}
};
let mut signals = Vec::new();
for (scope, kind, disabled) in [("first", "person", true), ("second", "company", false)] {
let html = render(scope, kind).await;
let marker = "::topcoat::signal({"t":"signal","id":"";
let at = html.find(marker).expect("the condition's signal") + marker.len();
signals.push(html[at..at + 32].to_string());
for expected in [
format!("id=\"{scope}-vat\""),
format!("for=\"{scope}-vat\""),
format!("id=\"{scope}-kind\""),
] {
assert!(html.contains(&expected), "{expected} in {html}");
}
let input = html.find("name=\"vat\"").expect("the vat input");
let fieldset = &html[html[..input].rfind("<fieldset").expect("its fieldset")..input];
let tag = &fieldset[..fieldset.find('>').expect("a closed tag")];
assert_eq!(
tag.contains(" disabled"),
disabled,
"{scope} follows its own kind={kind}, got {tag}"
);
}
assert_ne!(
signals[0], signals[1],
"each form's condition has its own signal"
);
}
#[test]
fn a_condition_must_watch_a_placed_field() {
let kind = kind();
let vat = Field::text(Customer::fields().vat()).visible_when(&kind, ["company"]);
assert_eq!(
Schema::new(vat).declaration_errors(),
[DeclarationErrorKind::UnplacedWatchedField {
field: "kind".to_string()
}]
);
}
#[test]
fn a_conditional_field_needs_a_blank_answer() {
let kind = kind();
let note = Field::text(Customer::fields().note()).required();
let block = Section::new("Company")
.schema(note)
.visible_when(&kind, ["company"]);
assert_eq!(
Schema::new((kind, block)).declaration_errors(),
[DeclarationErrorKind::RequiredConditionalField {
field: "note".to_string()
}]
);
}
#[test]
fn a_hidden_watched_field_guards_only_inside_its_block() {
let nested = || {
let kind = kind();
let company = Field::choice(Customer::fields().company()).options(["eu", "other"]);
let vat = Field::text(Customer::fields().vat()).visible_when(&company, ["eu"]);
(kind, company, vat)
};
let (kind, company, vat) = nested();
let inside = Group::new()
.schema((company, vat))
.visible_when(&kind, ["company"]);
assert!(Schema::new((kind, inside)).declaration_errors().is_empty());
let (kind, company, vat) = nested();
let outside = Group::new()
.schema(company)
.visible_when(&kind, ["company"]);
assert_eq!(
Schema::new((kind, outside, vat)).declaration_errors(),
[DeclarationErrorKind::HiddenWatchedField {
field: "company".to_string()
}]
);
}
#[test]
fn a_toggle_without_a_value_reads_as_unchecked() {
let active = Field::toggle(Customer::fields().active());
let note = Field::text(Customer::fields().note()).visible_when(&active, ["false"]);
let schema = Schema::new((active, note));
assert!(schema.condition_hidden(&FormValues::new()).is_empty());
let checked = FormValues::from_iter([("active".to_string(), "true".to_string())]);
assert_eq!(schema.condition_hidden(&checked), ["note"]);
}
#[test]
fn a_condition_names_values_the_watched_field_posts() {
let kind = kind();
let vat = Field::text(Customer::fields().vat()).visible_when(&kind, ["Company"]);
let active = Field::toggle(Customer::fields().active());
let note = Field::text(Customer::fields().note()).visible_when(&active, ["yes", "true"]);
assert_eq!(
Schema::new((kind, vat, active, note)).declaration_errors(),
[
DeclarationErrorKind::UnpostedConditionValue {
field: "kind".to_string(),
value: "Company".to_string()
},
DeclarationErrorKind::UnpostedConditionValue {
field: "active".to_string(),
value: "yes".to_string()
},
]
);
}