use crate::e2e::codegen::assertion_type_skip::{
streaming_assertion_type_skip_line, streaming_assertion_value_skip_line,
};
use crate::e2e::codegen::field_skip::{FieldSkip, nested_wildcard_skip_line};
use crate::e2e::codegen::payload_union_skip::{UnionLoweringTarget, payload_union_skip_line};
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;
mod tagged_union;
use tagged_union::try_render_tagged_union_assertion;
const BARE_RESULT_TOKEN: &str = "<bare result>";
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,
) {
if !result_is_simple && let Some(f) = assertion.field.as_deref() {
if !f.is_empty() && !field_resolver.is_valid_for_result(f) {
let _ = writeln!(
out,
" // skipped: {}",
FieldSkip::NotAvailableOnDartResultType.message(f)
);
return;
}
}
if let Some(field) = assertion.field.as_deref()
&& !field.is_empty()
&& try_render_tagged_union_assertion(out, assertion, result_var, field_resolver, field)
{
return;
}
if let Some(field) = assertion.field.as_deref()
&& !field.is_empty()
&& field_resolver.tagged_union_split(field).is_some()
{
let _ = writeln!(
out,
" // skipped: {}",
FieldSkip::CrossesTaggedUnionBoundaryInDart.message(field)
);
return;
}
if let Some(f) = assertion.field.as_deref()
&& 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..]),
};
if let Some(line) = nested_wildcard_skip_line(" ", "//", f, elem_part) {
let _ = writeln!(out, "{line}");
return;
}
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);
let elem_is_enum = field_resolver.is_enum(f) || field_resolver.is_enum(resolved_full);
let elem_to_string = if elem_is_enum {
format!("e.{elem_accessor}.runtimeType.toString()")
} else {
format!("e.{elem_accessor}.toString()")
};
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) => {elem_to_string}.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) => {elem_to_string}.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) => {elem_to_string}.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) => {elem_to_string}.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;
}
if let Some(line) = payload_union_skip_line(
" ",
"//",
field_resolver,
assertion.field.as_deref(),
UnionLoweringTarget::Dart,
) {
let _ = writeln!(out, "{line}");
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()
.filter(|f| !f.is_empty())
.is_some_and(|f| field_resolver.is_enum(f));
let is_display_as_text = assertion
.field
.as_deref()
.map(|f| {
let resolved = field_resolver.resolve(f);
field_resolver.is_display_as_text(f) || field_resolver.is_display_as_text(resolved)
})
.unwrap_or(false);
if expected.is_string() {
if is_enum_field {
let is_optional = assertion
.field
.as_deref()
.map(|f| {
let resolved = field_resolver.resolve(f);
field_resolver.is_optional(f) || field_resolver.is_optional(resolved)
})
.unwrap_or(false);
let wire_accessor = if is_optional {
format!("{field_accessor}?.wireValue")
} else {
format!("{field_accessor}.wireValue")
};
let _ = writeln!(out, " expect({wire_accessor}, equals({dart_val}));");
} else if is_display_as_text {
let is_optional = assertion
.field
.as_deref()
.map(|f| {
let resolved = field_resolver.resolve(f);
field_resolver.is_optional(f) || field_resolver.is_optional(resolved)
})
.unwrap_or(false);
if is_optional {
let _ = writeln!(
out,
" expect(({field_accessor}?.text() ?? ''), equals({dart_val}.toString()));"
);
} else {
let _ = writeln!(
out,
" expect({field_accessor}.text(), equals({dart_val}.toString()));"
);
}
} else {
let safe_accessor = if result_is_simple && assertion.field.is_none() {
format!("({field_accessor} ?? '').toString()")
} else {
format!("{field_accessor}.toString()")
};
let _ = writeln!(out, " expect({safe_accessor}, equals({dart_val}.toString()));");
}
} 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()
.filter(|f| !f.is_empty())
.is_some_and(|f| field_resolver.is_enum(f));
let is_display_as_text = assertion
.field
.as_deref()
.map(|f| {
let resolved = field_resolver.resolve(f);
field_resolver.is_display_as_text(f) || field_resolver.is_display_as_text(resolved)
})
.unwrap_or(false);
if expected.is_string() {
if is_enum_field {
let is_optional = assertion
.field
.as_deref()
.map(|f| {
let resolved = field_resolver.resolve(f);
field_resolver.is_optional(f) || field_resolver.is_optional(resolved)
})
.unwrap_or(false);
let wire_accessor = if is_optional {
format!("{field_accessor}?.wireValue")
} else {
format!("{field_accessor}.wireValue")
};
let _ = writeln!(out, " expect({wire_accessor}, isNot(equals({dart_val})));");
} else if is_display_as_text {
let is_optional = assertion
.field
.as_deref()
.map(|f| {
let resolved = field_resolver.resolve(f);
field_resolver.is_optional(f) || field_resolver.is_optional(resolved)
})
.unwrap_or(false);
if is_optional {
let _ = writeln!(
out,
" expect(({field_accessor}?.text() ?? '').trim(), isNot(equals({dart_val}.toString().trim())));"
);
} else {
let _ = writeln!(
out,
" expect({field_accessor}.text().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)
|| field_resolver.is_collection_root(f)
|| field_resolver.is_collection_root(resolved)
});
if is_collection {
let _ = writeln!(out, " expect({field_accessor}, isNotEmpty);");
} else {
let is_optional = assertion.field.as_deref().is_some_and(|field| {
let resolved = field_resolver.resolve(field);
field_resolver.is_optional(field) || field_resolver.is_optional(resolved)
});
let null_aware = if is_optional { "?" } else { "" };
let _ = writeln!(out, " expect({field_accessor}{null_aware}.toString(), isNotEmpty);");
}
}
"is_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)
|| field_resolver.is_collection_root(f)
|| field_resolver.is_collection_root(resolved)
});
if is_collection {
let _ = writeln!(out, " expect({field_accessor}, anyOf(isNull, isEmpty));");
} else {
let _ = writeln!(out, " expect(({field_accessor}?.toString() ?? ''), 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
&& 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
&& 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
&& 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
&& 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 is_optional = assertion.field.as_ref().is_some_and(|f| {
let resolved = field_resolver.resolve(f);
field_resolver.is_optional(f) || field_resolver.is_optional(resolved)
});
if is_optional {
let _ = writeln!(out, " expect({field_accessor}, isNotNull);");
} else {
let _ = writeln!(out, " expect({field_accessor}, isTrue);");
}
}
"is_false" => {
let is_optional = assertion.field.as_ref().is_some_and(|f| {
let resolved = field_resolver.resolve(f);
field_resolver.is_optional(f) || field_resolver.is_optional(resolved)
});
if is_optional {
let _ = writeln!(out, " expect({field_accessor}, isNull);");
} else {
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
&& 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}));");
} else {
let _ = writeln!(
out,
"{}",
streaming_assertion_value_skip_line(" ", "//", "chunks", &assertion.assertion_type)
);
}
}
"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}'));");
} else {
let _ = writeln!(
out,
"{}",
streaming_assertion_value_skip_line(" ", "//", "stream_content", &assertion.assertion_type)
);
}
}
_ => {
let _ = writeln!(
out,
"{}",
streaming_assertion_type_skip_line(
" ",
"//",
assertion.field.as_deref().unwrap_or(BARE_RESULT_TOKEN),
&assertion.assertion_type
)
);
}
}
}
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)
&& 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);"
))
}
#[cfg(test)]
mod tests;