use super::external_library_loader::{frb_init_prologue_replacement, rewrite_frb_external_library_loader};
use super::*;
#[test]
fn named_struct_payload_preserves_field0() {
let input = r#"sealed class FormatMetadata with _$FormatMetadata {
const FormatMetadata._();
const factory FormatMetadata.pdf({required PdfMetadata field0}) =
FormatMetadata_Pdf;
const factory FormatMetadata.docx({required DocxMetadata field0}) =
FormatMetadata_Docx;
}
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(
out.contains("required PdfMetadata field0"),
"PdfMetadata payload should keep `field0`, got:\n{out}"
);
assert!(
out.contains("required DocxMetadata field0"),
"DocxMetadata payload should keep `field0`, got:\n{out}"
);
}
#[test]
fn primitive_payload_preserves_field0() {
let input = r#" const factory OutputFormat.custom({required String field0}) =
OutputFormat_Custom;
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(
out.contains("required String field0"),
"String payload should keep `field0`, got:\n{out}"
);
}
#[test]
fn multi_field_tuple_preserves_field_indices() {
let input = r#" const factory Point.xy({required int field0, required int field1}) =
Point_Xy;
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(
out.contains("required int field0"),
"first tuple field should keep `field0`, got:\n{out}"
);
assert!(
out.contains("required int field1"),
"second tuple field should keep `field1`, got:\n{out}"
);
}
#[test]
fn named_struct_field_is_preserved() {
let input = r#" const factory Shape.rect({required double width, required double height}) =
Shape_Rect;
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(
out.contains("required double width"),
"named field `width` must be preserved, got:\n{out}"
);
assert!(
out.contains("required double height"),
"named field `height` must be preserved, got:\n{out}"
);
}
#[test]
fn non_variant_lines_are_untouched() {
let input = r#"// This file is automatically generated.
import 'package:freezed_annotation/freezed_annotation.dart';
Future<int> extractBytes({required List<int> content}) =>
RustLib.instance.api.crateExtractBytes(content: content);
class Foo {
final int field0;
Foo({required this.field0});
}
"#;
let out = rewrite_frb_sealed_variants(input);
assert_eq!(out, input, "non-variant code must round-trip unchanged");
}
#[test]
fn unrelated_payload_preserves_field0() {
let input = r#" const factory Drawable.image({required Bitmap field0}) =
Drawable_Image;
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(
out.contains("required Bitmap field0"),
"unrelated payload type should keep `field0`, got:\n{out}"
);
}
#[test]
fn nullable_payload_preserves_field0() {
let input = r#" const factory Either.left({required LeftValue? field0}) =
Either_Left;
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(
out.contains("required LeftValue? field0"),
"nullable payload should keep `field0`, got:\n{out}"
);
}
#[test]
fn realistic_sample_crate_format_metadata_block_preserves_field0() {
let input = r#"sealed class FormatMetadata with _$FormatMetadata {
const FormatMetadata._();
const factory FormatMetadata.pdf({required PdfMetadata field0}) =
FormatMetadata_Pdf;
const factory FormatMetadata.docx({required DocxMetadata field0}) =
FormatMetadata_Docx;
const factory FormatMetadata.excel({required ExcelMetadata field0}) =
FormatMetadata_Excel;
const factory FormatMetadata.code({required String field0}) =
FormatMetadata_Code;
}
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(out.contains("required PdfMetadata field0"));
assert!(out.contains("required DocxMetadata field0"));
assert!(out.contains("required ExcelMetadata field0"));
assert!(out.contains("required String field0"));
assert!(out.contains("sealed class FormatMetadata"));
assert!(out.contains("FormatMetadata_Pdf"));
}
#[test]
fn idempotent_when_run_twice() {
let input = r#" const factory FormatMetadata.pdf({required PdfMetadata field0}) =
FormatMetadata_Pdf;
"#;
let once = rewrite_frb_sealed_variants(input);
let twice = rewrite_frb_sealed_variants(&once);
assert_eq!(once, twice, "rewriter must be idempotent");
}
#[test]
fn multiple_distinct_sealed_class_variants_preserve_field0() {
let input = r#"sealed class FormatMetadata with _$FormatMetadata {
const FormatMetadata._();
const factory FormatMetadata.pdf({required PdfMetadata field0}) =
FormatMetadata_Pdf;
const factory FormatMetadata.docx({required DocxMetadata field0}) =
FormatMetadata_Docx;
}
sealed class OutputFormat with _$OutputFormat {
const OutputFormat._();
const factory OutputFormat.custom({required String field0}) =
OutputFormat_Custom;
const factory OutputFormat.json({required JsonConfig field0}) =
OutputFormat_Json;
}
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(out.contains("required PdfMetadata field0"));
assert!(out.contains("required DocxMetadata field0"));
assert!(out.contains("required String field0"));
assert!(out.contains("required JsonConfig field0"));
}
fn frb_generated_fixture() -> &'static str {
r#"// @generated by `flutter_rust_bridge`@ 2.12.0.
import 'dart:async';
import 'dart:convert';
import 'frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
RustLib._();
/// Initialize flutter_rust_bridge
static Future<void> init({
RustLibApi? api,
BaseHandler? handler,
ExternalLibrary? externalLibrary,
bool forceSameCodegenVersion = true,
}) async {
await instance.initImpl(
api: api,
handler: handler,
externalLibrary: externalLibrary,
forceSameCodegenVersion: forceSameCodegenVersion,
);
}
static const kDefaultExternalLibraryLoaderConfig =
ExternalLibraryLoaderConfig(
stem: 'sample_router_dart',
ioDirectory: 'rust/target/release/',
webPrefix: 'pkg/',
wasmBindgenName: 'wasm_bindgen',
);
}
"#
}
#[test]
fn loader_rewrite_injecsample_package_relative_resolution() {
let out = rewrite_frb_external_library_loader(
frb_generated_fixture(),
"sample_router",
"sample_router",
"sample_router_dart",
);
assert!(
out.contains("externalLibrary ??= await _alefResolveExternalLibrary();"),
"init must resolve the package-relative library, got:\n{out}"
);
assert!(
out.contains("Isolate.resolvePackageUri(_DartCore.Uri.parse('package:sample_router/sample_router.dart'))"),
"loader must resolve the package URI, got:\n{out}"
);
assert!(
out.contains("src/sample_router_bridge_generated/"),
"loader must target the bridge-generated dir, got:\n{out}"
);
assert!(
out.contains("'sample_router_dart.framework/sample_router_dart'"),
"missing macOS framework candidate, got:\n{out}"
);
assert!(
out.contains("'libsample_router_dart.dylib'"),
"missing macOS dylib candidate, got:\n{out}"
);
assert!(
out.contains("'libsample_router_dart.so'"),
"missing linux candidate, got:\n{out}"
);
assert!(
out.contains("'sample_router_dart.dll'"),
"missing windows candidate, got:\n{out}"
);
assert!(out.contains("import 'dart:io';"), "must import dart:io, got:\n{out}");
assert!(
out.contains("import 'dart:isolate';"),
"must import dart:isolate, got:\n{out}"
);
}
#[test]
fn loader_rewrite_is_idempotent() {
let once = rewrite_frb_external_library_loader(
frb_generated_fixture(),
"sample_router",
"sample_router",
"sample_router_dart",
);
let twice = rewrite_frb_external_library_loader(&once, "sample_router", "sample_router", "sample_router_dart");
assert_eq!(once, twice, "loader rewrite must be idempotent");
assert_eq!(
twice.matches("import 'dart:io';").count(),
1,
"imports must not duplicate"
);
assert_eq!(
twice.matches("_alefResolveExternalLibrary() async").count(),
1,
"helper must not be injected twice"
);
}
#[test]
fn loader_rewrite_is_noop_without_init_prologue() {
let input = "// just some dart\nFuture<int> foo() async => 1;\n";
assert_eq!(
rewrite_frb_external_library_loader(input, "sample_router", "sample_router", "sample_router_dart"),
input
);
}
#[test]
fn sealed_variant_rewrite_also_applies_loader_fix_via_stem() {
let out = rewrite_frb_sealed_variants(frb_generated_fixture());
assert!(
out.contains("externalLibrary ??= await _alefResolveExternalLibrary();"),
"sealed-variant pass must also inject the loader, got:\n{out}"
);
assert!(
out.contains("Isolate.resolvePackageUri(_DartCore.Uri.parse('package:sample_router/sample_router.dart'))"),
"package derived from stem must be `sample_router`, got:\n{out}"
);
}
#[test]
fn sealed_variant_rewrite_leaves_lib_dart_loader_untouched() {
let input = r#"import 'frb_generated.dart';
Future<int> extractBytes({required List<int> content}) =>
RustLib.instance.api.crateExtractBytes(content: content);
"#;
let out = rewrite_frb_sealed_variants(input);
assert!(
!out.contains("_alefResolveExternalLibrary"),
"lib.dart must not get a loader, got:\n{out}"
);
}
#[test]
fn loader_rewrite_includes_rid_aware_path() {
let out = rewrite_frb_external_library_loader(
frb_generated_fixture(),
"sample_router",
"sample_router",
"sample_router_dart",
);
assert!(
out.contains("src/native/"),
"loader must check RID-aware path (src/native/<rid>/), got:\n{out}"
);
assert!(
out.contains("computeRid()"),
"loader must compute RID from platform and arch, got:\n{out}"
);
assert!(
out.contains("Platform.operatingSystem"),
"loader must detect operating system, got:\n{out}"
);
assert!(
out.contains("'linux-x64'") || out.contains("linux-"),
"loader must support linux RID variants, got:\n{out}"
);
assert!(
out.contains("'macos-arm64'") || out.contains("macos-"),
"loader must support macos RID variants, got:\n{out}"
);
assert!(
out.contains("'windows-x64'") || out.contains("windows-"),
"loader must support windows RID variants, got:\n{out}"
);
let rid_pos = out.find("src/native/").expect("RID path must exist");
let legacy_pos = out
.find("src/sample_router_bridge_generated/")
.expect("legacy path must exist");
assert!(
rid_pos < legacy_pos,
"RID-aware check must come before legacy fallback, got:\n{out}"
);
}
#[test]
fn filter_excluded_functions_removes_multiline_functions() {
let input = r#"/// Pass `metadata` as `null` when the caller has no extraction metadata available;
/// the metadata bonus simply isn't applied in that case. Texts shorter than
/// `MIN_TEXT_LENGTH` short-circuit to `0.1` regardless of metadata.
Future<double> calculateQualityScore({
required String text,
Map<String, String>? metadata,
}) => RustLib.instance.api.crateCalculateQualityScore(
text: text,
metadata: metadata,
);
Future<ExtractionResult> extractBytes(
{required Uint8List content, required String mimeType}) =>
RustLib.instance.api.crateExtractBytes(content: content, mimeType: mimeType);
"#;
let exclude_set = std::collections::HashSet::from(["calculate_quality_score"]);
let out = filter_excluded_functions(input, &exclude_set);
assert!(
!out.contains("calculateQualityScore"),
"excluded function calculateQualityScore must be removed, got:\n{out}"
);
assert!(
!out.contains("MIN_TEXT_LENGTH"),
"doc comments for excluded function must be removed, got:\n{out}"
);
assert!(
out.contains("extractBytes"),
"non-excluded function extractBytes must remain, got:\n{out}"
);
}
#[test]
fn fix_handler_executor_calls_adds_async_to_closures() {
let input = r#"Future<String> processRequest(Request req, FutureOr<String> Function(String) handler) {
return handler.executeNormal(
SyncTask(request: req),
);
}
int handleRoute(RouteData route, FutureOr<int> Function(String) handler) {
return handler.executeSync(
RouteTask(data: route),
);
}
"#;
let out = fix_handler_executor_calls(input);
assert!(
out.contains(".executeNormal();") || out.contains(".executeSync();"),
"expected task executor method calls in output, got:\n{out}"
);
assert!(
!out.contains("handler("),
"handler callback should NOT be invoked directly (it's serialized), got:\n{out}"
);
assert!(
!out.contains("handler.executeSync(") && !out.contains("handler.executeNormal("),
"handler.executeSync/executeNormal method calls should be removed (moved to task), got:\n{out}"
);
assert!(
!out.contains("await await"),
"should not have duplicate awaits, got:\n{out}"
);
}
#[test]
fn fix_handler_executor_calls_preserves_super_handler_without_parameter() {
let input = r#" @override
App crateServiceApiAppNew() {
return handler.executeSync(
SyncTask(
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 5)!;
},
codec: SseCodec(
decodeSuccessData: sse_decode_app,
decodeErrorData: null,
),
constMeta: kConstMeta,
argValues: [],
apiImpl: this,
),
);
}
"#;
let out = fix_handler_executor_calls(input);
assert!(
out.contains("handler.executeSync("),
"handler.executeSync should be preserved when handler is not a parameter, got:\n{out}"
);
assert!(
!out.contains("handler.executeNormal("),
"no executeNormal should be present in input"
);
}
#[test]
fn fix_handler_executor_calls_rewrites_when_handler_is_parameter() {
let input = r#" @override
int crateServiceApiAppConnect({
required App that,
required String path,
required FutureOr<String> Function(String) handler,
}) {
return handler.executeSync(
SyncTask(
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_App(that, serializer);
sse_encode_String(path, serializer);
return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 1)!;
},
codec: SseCodec(
decodeSuccessData: sse_decode_i_32,
decodeErrorData: null,
),
constMeta: kConstMeta,
argValues: [that, path, handler],
apiImpl: this,
),
);
}
"#;
let out = fix_handler_executor_calls(input);
assert!(
!out.contains("handler.executeSync("),
"handler.executeSync should be rewritten when handler is a parameter, got:\n{out}"
);
assert!(
out.contains(".executeSync();"),
"expected `.executeSync();` on the task in output (rewritten), got:\n{out}"
);
assert!(
!out.contains("handler("),
"handler callback should NOT be invoked directly (it's serialized), got:\n{out}"
);
}
#[test]
fn fix_handler_executor_calls_strips_trailing_comma_in_wrapper() {
let input = r#" Future<DartHandlerHandler> crateServiceApiDartHandlerHandlerNew({
required FutureOr<String> Function(String) handler,
}) {
return await handler.executeNormal(
NormalTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_DartFn_Inputs_String_Output_String_AnyhowException(
handler,
serializer,
);
},
codec: SseCodec(
decodeSuccessData: sse_decode_Auto_Owned_RustOpaque,
decodeErrorData: null,
),
constMeta: kConstMeta,
argValues: [handler],
apiImpl: this,
),
);
}
"#;
let out = fix_handler_executor_calls(input);
assert!(
out.contains("return await") && out.contains("NormalTask("),
"NormalTask should follow return await, got:\n{out}"
);
assert!(
!out.contains("handler.executeNormal("),
"handler.executeNormal should be removed, got:\n{out}"
);
assert!(
out.contains(".executeNormal();"),
"should have .executeNormal(); on the task, got:\n{out}"
);
assert!(
!out.contains(")).executeNormal();"),
"should not have double closing parens, got:\n{out}"
);
}
#[test]
fn fix_handler_executor_calls_removes_invalid_async_from_class_declaration() {
let input = r#"class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi async {
RustLibApiImpl({
required super.handler,
required super.wire,
required super.generalizedFrbRustBinding,
required super.portManager,
});
@override
int crateServiceApiAppConnect({
required App that,
required String path,
required FutureOr<String> Function(String) handler,
}) async {
return await handler("test");
}
}
"#;
let out = fix_handler_executor_calls(input);
assert!(
!out.contains("implements RustLibApi async {"),
"class declaration should not have `async` keyword, got:\n{out}"
);
assert!(
out.contains("implements RustLibApi {"),
"class declaration should have closing brace without `async`, got:\n{out}"
);
assert!(
out.contains(") async {") || out.contains("async {"),
"method signatures should still be able to use `async`, got:\n{out}"
);
}
#[test]
fn default_field_rewriter_is_identity_without_ir_metadata() {
let input = r#"class GenericOptions {
final String mode;
final bool enabled;
final PlatformInt64 retryCount;
const GenericOptions({
required this.mode,
required this.enabled,
required this.retryCount,
});
}
"#;
let out = make_struct_fields_with_defaults_optional(input);
assert_eq!(out, input, "post-FRB rewriter must not infer defaults by class name");
}
#[test]
fn prologue_replacement_helper_closes_before_init_opens() {
let replacement = frb_init_prologue_replacement("test_pkg", "test_mod", "test_stem");
let escaped_open = replacement.matches("{{").count();
let escaped_close = replacement.matches("}}").count();
assert_eq!(
escaped_open, escaped_close,
"escaped brace mismatch (must be in pairs for format escaping): {} {{ vs {} }}",
escaped_open, escaped_close
);
assert!(
replacement.contains("static Future<ExternalLibrary?> _alefResolveExternalLibrary()"),
"helper method signature must exist"
);
assert!(
replacement.contains("static Future<void> init({"),
"init method signature must exist"
);
let helper_ret_null = replacement.find("return null;").expect("helper must have return null");
let init_sig = replacement
.find("static Future<void> init({")
.expect("init sig must exist");
assert!(helper_ret_null < init_sig, "helper return must precede init signature");
}
#[test]
fn prologue_replacement_uses_abi_current_for_arch_detection() {
let replacement = frb_init_prologue_replacement("test_pkg", "test_mod", "test_stem");
assert!(
replacement.contains("final abi = Abi.current();"),
"must use Abi.current() for architecture detection, got:\n{replacement}"
);
assert!(replacement.contains("Abi.linuxX64"), "must check Abi.linuxX64");
assert!(replacement.contains("Abi.linuxArm64"), "must check Abi.linuxArm64");
assert!(replacement.contains("Abi.macosX64"), "must check Abi.macosX64");
assert!(replacement.contains("Abi.macosArm64"), "must check Abi.macosArm64");
assert!(replacement.contains("Abi.windowsX64"), "must check Abi.windowsX64");
assert!(replacement.contains("Abi.windowsArm64"), "must check Abi.windowsArm64");
assert!(replacement.contains("'linux-x64'"), "must map to linux-x64 RID");
assert!(replacement.contains("'macos-x64'"), "must map to macos-x64 RID");
}
#[test]
fn fix_handler_executor_strips_orphaned_paren_sync_task() {
let input = r#" @override
int crateServiceApiAppConnect({
required App that,
required String path,
required FutureOr<String> Function(String) handler,
}) {
return
SyncTask(
callFfi: () {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_App(that, serializer);
sse_encode_String(path, serializer);
return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 1)!;
},
codec: SseCodec(
decodeSuccessData: sse_decode_i_32,
decodeErrorData: null,
),
constMeta: kConstMeta,
argValues: [that, path, handler],
apiImpl: this,
),
).executeSync();
}
"#;
let out = fix_handler_executor_calls(input);
assert!(
out.contains(").executeSync();"),
"should have ).executeSync(); without orphaned paren, got:\n{out}"
);
let lint_check = out.matches(")).executeSync()").count();
assert_eq!(
lint_check, 0,
"should not have )).executeSync() (double paren), got:\n{out}"
);
}
#[test]
fn fix_handler_executor_strips_orphaned_paren_async_task() {
let input = r#" @override
Future<String> crateServiceApiAppHandlerCall({
required App that,
required String path,
required FutureOr<String> Function(String) handler,
}) async {
return await
AsyncTask(
callFfi: (port_) {
final serializer = SseSerializer(generalizedFrbRustBinding);
sse_encode_App(that, serializer);
sse_encode_String(path, serializer);
return pdeCallFfiAsync(generalizedFrbRustBinding, serializer, funcId: 42);
},
codec: SseCodec(
decodeSuccessData: sse_decode_String,
decodeErrorData: null,
),
constMeta: kConstMeta,
argValues: [that, path, handler],
apiImpl: this,
),
).executeNormal();
}
"#;
let out = fix_handler_executor_calls(input);
assert!(
out.contains(").executeNormal();"),
"should have ).executeNormal(); without orphaned paren, got:\n{out}"
);
let lint_check = out.matches(")).executeNormal()").count();
assert_eq!(
lint_check, 0,
"should not have )).executeNormal() (double paren), got:\n{out}"
);
}