use crate::e2e::field_access::FieldResolver;
use crate::e2e::fixture::Assertion;
use heck::ToLowerCamelCase;
use std::fmt::Write as FmtWrite;
use super::values::escape_dart;
pub(super) fn dart_length_expr(field_accessor: &str, field: Option<&str>, field_resolver: &FieldResolver) -> String {
let is_optional = field
.map(|f| {
let resolved = field_resolver.resolve(f);
field_resolver.is_optional(f) || field_resolver.is_optional(resolved)
})
.unwrap_or(false);
if is_optional {
format!("{field_accessor}?.length ?? 0")
} else {
format!("{field_accessor}.length")
}
}
pub(super) fn dart_format_value(val: &serde_json::Value) -> String {
match val {
serde_json::Value::String(s) => format!("'{}'", escape_dart(s)),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Null => "null".to_string(),
other => format!("'{}'", escape_dart(&other.to_string())),
}
}
pub(super) fn render_assertion_dart(
out: &mut String,
assertion: &Assertion,
result_var: &str,
result_is_simple: bool,
field_resolver: &FieldResolver,
enum_fields: &std::collections::HashSet<String>,
) {
if !result_is_simple {
if let Some(f) = assertion.field.as_deref() {
let head = f.split("[].").next().unwrap_or(f);
if !head.is_empty() && !field_resolver.is_valid_for_result(head) {
let _ = writeln!(out, " // skipped: field '{f}' not available on dart result type");
return;
}
}
}
if let Some(f) = assertion.field.as_deref() {
if !f.is_empty() && field_resolver.tagged_union_split(f).is_some() {
let _ = writeln!(
out,
" // skipped: field '{f}' crosses a tagged-union variant boundary (not expressible in Dart)"
);
return;
}
}
if let Some(f) = assertion.field.as_deref() {
if let Some(dot) = f.find("[].") {
let resolved_full = field_resolver.resolve(f);
let (array_part, elem_part) = match resolved_full.find("[].") {
Some(rdot) => (&resolved_full[..rdot], &resolved_full[rdot + 3..]),
None => (&f[..dot], &f[dot + 3..]),
};
let array_accessor = if array_part.is_empty() {
result_var.to_string()
} else {
field_resolver.accessor(array_part, "dart", result_var)
};
let elem_accessor = field_to_dart_accessor(elem_part);
match assertion.assertion_type.as_str() {
"contains" => {
if let Some(expected) = &assertion.value {
let dart_val = dart_format_value(expected);
let _ = writeln!(
out,
" expect({array_accessor}.any((e) => e.{elem_accessor}.toString().contains({dart_val})), isTrue);"
);
}
}
"contains_all" => {
if let Some(values) = &assertion.values {
for val in values {
let dart_val = dart_format_value(val);
let _ = writeln!(
out,
" expect({array_accessor}.any((e) => e.{elem_accessor}.toString().contains({dart_val})), isTrue);"
);
}
}
}
"not_contains" => {
if let Some(expected) = &assertion.value {
let dart_val = dart_format_value(expected);
let _ = writeln!(
out,
" expect({array_accessor}.any((e) => e.{elem_accessor}.toString().contains({dart_val})), isFalse);"
);
} else if let Some(values) = &assertion.values {
for val in values {
let dart_val = dart_format_value(val);
let _ = writeln!(
out,
" expect({array_accessor}.any((e) => e.{elem_accessor}.toString().contains({dart_val})), isFalse);"
);
}
}
}
"not_empty" => {
let _ = writeln!(
out,
" expect({array_accessor}.any((e) => e.{elem_accessor}.toString().isNotEmpty), isTrue);"
);
}
other => {
let _ = writeln!(
out,
" // skipped: unsupported traversal assertion '{other}' on '{f}'"
);
}
}
return;
}
}
let field_accessor = if result_is_simple {
result_var.to_string()
} else {
match assertion.field.as_deref() {
Some(f) if !f.is_empty() => field_resolver.accessor(f, "dart", result_var),
_ => result_var.to_string(),
}
};
let format_value = |val: &serde_json::Value| -> String { dart_format_value(val) };
match assertion.assertion_type.as_str() {
"equals" | "field_equals" => {
if let Some(expected) = &assertion.value {
let dart_val = format_value(expected);
let is_enum_field = assertion
.field
.as_deref()
.map(|f| {
let resolved = field_resolver.resolve(f);
enum_fields.contains(f) || enum_fields.contains(resolved)
})
.unwrap_or(false);
if expected.is_string() {
if is_enum_field {
let _ = writeln!(
out,
" expect(_alefE2eText({field_accessor}).trim(), equals({dart_val}.toString().trim()));"
);
} else {
let safe_accessor = if result_is_simple && assertion.field.is_none() {
format!("({field_accessor} ?? '').toString().trim()")
} else {
format!("{field_accessor}.toString().trim()")
};
let _ = writeln!(
out,
" expect({safe_accessor}, equals({dart_val}.toString().trim()));"
);
}
} else {
let _ = writeln!(out, " expect({field_accessor}, equals({dart_val}));");
}
} else {
let _ = writeln!(
out,
" // skipped: '{}' assertion missing value",
assertion.assertion_type
);
}
}
"not_equals" => {
if let Some(expected) = &assertion.value {
let dart_val = format_value(expected);
let is_enum_field = assertion
.field
.as_deref()
.map(|f| {
let resolved = field_resolver.resolve(f);
enum_fields.contains(f) || enum_fields.contains(resolved)
})
.unwrap_or(false);
if expected.is_string() {
if is_enum_field {
let _ = writeln!(
out,
" expect(_alefE2eText({field_accessor}).trim(), isNot(equals({dart_val}.toString().trim())));"
);
} else {
let safe_accessor = if result_is_simple && assertion.field.is_none() {
format!("({field_accessor} ?? '').toString().trim()")
} else {
format!("{field_accessor}.toString().trim()")
};
let _ = writeln!(
out,
" expect({safe_accessor}, isNot(equals({dart_val}.toString().trim())));"
);
}
} else {
let _ = writeln!(out, " expect({field_accessor}, isNot(equals({dart_val})));");
}
}
}
"contains" => {
if let Some(expected) = &assertion.value {
let dart_val = format_value(expected);
let aggregator = dart_stringy_aggregator_contains_assert(
assertion.field.as_deref(),
result_var,
field_resolver,
&dart_val,
);
if let Some(line) = aggregator {
let _ = writeln!(out, "{line}");
} else {
let _ = writeln!(out, " expect({field_accessor}, contains({dart_val}));");
}
} else {
let _ = writeln!(out, " // skipped: 'contains' assertion missing value");
}
}
"contains_all" => {
if let Some(values) = &assertion.values {
for val in values {
let dart_val = format_value(val);
let _ = writeln!(out, " expect({field_accessor}, contains({dart_val}));");
}
}
}
"contains_any" => {
if let Some(values) = &assertion.values {
let checks: Vec<String> = values
.iter()
.map(|v| {
let dart_val = format_value(v);
format!("{field_accessor}.contains({dart_val})")
})
.collect();
let joined = checks.join(" || ");
let _ = writeln!(out, " expect({joined}, isTrue);");
}
}
"not_contains" => {
if let Some(expected) = &assertion.value {
let dart_val = format_value(expected);
let _ = writeln!(out, " expect({field_accessor}, isNot(contains({dart_val})));");
} else if let Some(values) = &assertion.values {
for val in values {
let dart_val = format_value(val);
let _ = writeln!(out, " expect({field_accessor}, isNot(contains({dart_val})));");
}
}
}
"not_empty" => {
let is_collection = assertion.field.as_deref().is_some_and(|f| {
let resolved = field_resolver.resolve(f);
field_resolver.is_array(f) || field_resolver.is_array(resolved)
});
if is_collection {
let _ = writeln!(out, " expect({field_accessor}, isNotEmpty);");
} else {
let _ = writeln!(out, " expect({field_accessor}, isNotNull);");
}
}
"is_empty" => {
let _ = writeln!(out, " expect({field_accessor}, anyOf(isNull, isEmpty));");
}
"starts_with" => {
if let Some(expected) = &assertion.value {
let dart_val = format_value(expected);
let _ = writeln!(out, " expect({field_accessor}, startsWith({dart_val}));");
}
}
"ends_with" => {
if let Some(expected) = &assertion.value {
let dart_val = format_value(expected);
let _ = writeln!(out, " expect({field_accessor}, endsWith({dart_val}));");
}
}
"min_length" => {
if let Some(val) = &assertion.value {
if let Some(n) = val.as_u64() {
let length_expr = dart_length_expr(&field_accessor, assertion.field.as_deref(), field_resolver);
let _ = writeln!(out, " expect({length_expr}, greaterThanOrEqualTo({n}));");
}
}
}
"max_length" => {
if let Some(val) = &assertion.value {
if let Some(n) = val.as_u64() {
let length_expr = dart_length_expr(&field_accessor, assertion.field.as_deref(), field_resolver);
let _ = writeln!(out, " expect({length_expr}, lessThanOrEqualTo({n}));");
}
}
}
"count_equals" => {
if let Some(val) = &assertion.value {
if let Some(n) = val.as_u64() {
let length_expr = dart_length_expr(&field_accessor, assertion.field.as_deref(), field_resolver);
let _ = writeln!(out, " expect({length_expr}, equals({n}));");
}
}
}
"count_min" => {
if let Some(val) = &assertion.value {
if let Some(n) = val.as_u64() {
let length_expr = dart_length_expr(&field_accessor, assertion.field.as_deref(), field_resolver);
let _ = writeln!(out, " expect({length_expr}, greaterThanOrEqualTo({n}));");
}
}
}
"matches_regex" => {
if let Some(expected) = &assertion.value {
let dart_val = format_value(expected);
let _ = writeln!(out, " expect({field_accessor}, matches(RegExp({dart_val})));");
}
}
"is_true" => {
let _ = writeln!(out, " expect({field_accessor}, isTrue);");
}
"is_false" => {
let _ = writeln!(out, " expect({field_accessor}, isFalse);");
}
"greater_than" => {
if let Some(val) = &assertion.value {
let dart_val = format_value(val);
let _ = writeln!(out, " expect({field_accessor}, greaterThan({dart_val}));");
}
}
"less_than" => {
if let Some(val) = &assertion.value {
let dart_val = format_value(val);
let _ = writeln!(out, " expect({field_accessor}, lessThan({dart_val}));");
}
}
"greater_than_or_equal" => {
if let Some(val) = &assertion.value {
let dart_val = format_value(val);
let _ = writeln!(out, " expect({field_accessor}, greaterThanOrEqualTo({dart_val}));");
}
}
"less_than_or_equal" => {
if let Some(val) = &assertion.value {
let dart_val = format_value(val);
let _ = writeln!(out, " expect({field_accessor}, lessThanOrEqualTo({dart_val}));");
}
}
"not_null" => {
let _ = writeln!(out, " expect({field_accessor}, isNotNull);");
}
"not_error" => {
}
"error" => {
}
"method_result" => {
if let Some(method) = &assertion.method {
let dart_method = method.to_lower_camel_case();
let check = assertion.check.as_deref().unwrap_or("not_null");
let method_call = format!("{field_accessor}.{dart_method}()");
match check {
"equals" => {
if let Some(expected) = &assertion.value {
let dart_val = format_value(expected);
let _ = writeln!(out, " expect({method_call}, equals({dart_val}));");
}
}
"is_true" => {
let _ = writeln!(out, " expect({method_call}, isTrue);");
}
"is_false" => {
let _ = writeln!(out, " expect({method_call}, isFalse);");
}
"greater_than_or_equal" => {
if let Some(val) = &assertion.value {
let dart_val = format_value(val);
let _ = writeln!(out, " expect({method_call}, greaterThanOrEqualTo({dart_val}));");
}
}
"count_min" => {
if let Some(val) = &assertion.value {
if let Some(n) = val.as_u64() {
let _ = writeln!(out, " expect({method_call}.length, greaterThanOrEqualTo({n}));");
}
}
}
_ => {
let _ = writeln!(out, " expect({method_call}, isNotNull);");
}
}
}
}
other => {
let _ = writeln!(out, " // skipped: unknown assertion type '{other}'");
}
}
}
pub(super) fn render_streaming_assertion_dart(out: &mut String, assertion: &Assertion, result_var: &str) {
match assertion.assertion_type.as_str() {
"not_error" => {
let _ = writeln!(out, " expect({result_var}, isNotNull);");
}
"count_min" if assertion.field.as_deref() == Some("chunks") => {
if let Some(serde_json::Value::Number(n)) = &assertion.value {
let _ = writeln!(out, " expect({result_var}.length, greaterThanOrEqualTo({n}));");
}
}
"equals" if assertion.field.as_deref() == Some("stream_content") => {
if let Some(serde_json::Value::String(expected)) = &assertion.value {
let escaped = escape_dart(expected);
let _ = writeln!(
out,
" final _content = {result_var}.map((c) => c.choices.firstOrNull?.delta.content ?? '').join();"
);
let _ = writeln!(out, " expect(_content, equals('{escaped}'));");
}
}
other => {
let _ = writeln!(out, " // skipped streaming assertion: '{other}'");
}
}
}
pub(super) fn snake_to_camel(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let mut next_upper = false;
for ch in s.chars() {
if ch == '_' {
next_upper = true;
} else if next_upper {
result.extend(ch.to_uppercase());
next_upper = false;
} else {
result.push(ch);
}
}
result
}
pub(super) fn field_to_dart_accessor(path: &str) -> String {
let mut result = String::with_capacity(path.len());
for (i, segment) in path.split('.').enumerate() {
if i > 0 {
result.push('.');
}
if let Some(bracket_pos) = segment.find('[') {
let name = &segment[..bracket_pos];
let bracket = &segment[bracket_pos..];
result.push_str(&name.to_lower_camel_case());
result.push('!');
result.push_str(bracket);
} else {
result.push_str(&segment.to_lower_camel_case());
}
}
result
}
pub(super) fn dart_stringy_aggregator_contains_assert(
field: Option<&str>,
result_var: &str,
field_resolver: &crate::e2e::field_access::FieldResolver,
dart_val: &str,
) -> Option<String> {
use crate::e2e::field_access::StringyFieldKind;
let field = field?;
let resolved = field_resolver.resolve(field);
if resolved.contains('.') || resolved.contains('[') {
return None;
}
if !field_resolver.is_array(field) && !field_resolver.is_array(resolved) {
return None;
}
let array_accessor = field_resolver.accessor(field, "dart", result_var);
let root_type = field_resolver.dart_root_type().cloned();
if let Some(elem_type) = field_resolver.dart_advance(root_type.as_deref(), resolved) {
if let Some(stringy) = field_resolver.dart_stringy_fields(&elem_type) {
if stringy.len() >= 2 {
let mut texts_lines: Vec<String> = Vec::new();
for sf in stringy {
let call = sf.name.to_lower_camel_case();
match sf.kind {
StringyFieldKind::Plain => {
texts_lines.push(format!(" texts.add(item.{call}.toString());"));
}
StringyFieldKind::Optional => {
texts_lines.push(format!(
" final v_{call} = item.{call};\n if (v_{call} != null) texts.add(v_{call}.toString());"
));
}
StringyFieldKind::Vec => {
texts_lines.push(format!(
" texts.addAll(item.{call}.map((e) => e.toString()));"
));
}
}
}
let texts_block = texts_lines.join("\n");
return Some(format!(
" expect({array_accessor}.where((item) {{\n final texts = <String>[];\n{texts_block}\n return texts.any((t) => t.toLowerCase().contains(({dart_val}).toString().toLowerCase()));\n }}).isEmpty, isFalse);"
));
}
}
}
Some(format!(
" expect({array_accessor}.where((item) => item.toString().toLowerCase().contains(({dart_val}).toString().toLowerCase())).isEmpty, isFalse);"
))
}