diff --git a/modules/ensemble/lib/action/execute_action.dart b/modules/ensemble/lib/action/execute_action.dart index 77983cb89..93bb168ff 100644 --- a/modules/ensemble/lib/action/execute_action.dart +++ b/modules/ensemble/lib/action/execute_action.dart @@ -100,7 +100,7 @@ class ExecuteActionAction extends EnsembleAction { "Action '$name' contains an invalid 'body' payload."); } - return ScreenController() + return await ScreenController() .executeActionWithScope(context, childScope, innerAction); } finally { ActionScopeUtil.restorePageApisAfterAction(scopeManager, apiSnapshot); diff --git a/modules/ensemble/lib/action/wifi_action.dart b/modules/ensemble/lib/action/wifi_action.dart index c46b20237..b6e8608f7 100644 --- a/modules/ensemble/lib/action/wifi_action.dart +++ b/modules/ensemble/lib/action/wifi_action.dart @@ -19,9 +19,12 @@ Future _handleWifiResult( EnsembleAction? onError, bool? result, ) { - if (result == true && onSuccess != null) { - return ScreenController().executeAction(context, onSuccess, - event: EnsembleEvent(initiator, data: {'connected': true})); + if (result == true) { + if (onSuccess != null) { + return ScreenController().executeAction(context, onSuccess, + event: EnsembleEvent(initiator, data: {'connected': true})); + } + return Future.value(null); } if (onError != null) { final message = result == null diff --git a/modules/ensemble/test/action_scope_api_restore_test.dart b/modules/ensemble/test/action_scope_api_restore_test.dart index 2c80a5233..6678af104 100644 --- a/modules/ensemble/test/action_scope_api_restore_test.dart +++ b/modules/ensemble/test/action_scope_api_restore_test.dart @@ -96,4 +96,41 @@ void main() { expect(scopeManager.pageData.apiMap!['sharedApi'], same(pageApi)); }); }); + + group('executeAction async restore timing', () { + Future> runWithFinally({required bool awaitInner}) async { + final order = []; + + Future inner() async { + await Future.delayed(const Duration(milliseconds: 20)); + order.add('inner'); + } + + try { + if (awaitInner) { + await inner(); + } else { + // Mirrors ExecuteActionAction returning executeActionWithScope + // without await: finally runs before the inner Future completes. + // ignore: unawaited_futures + inner(); + } + } finally { + order.add('restore'); + } + return order; + } + + test('restore runs before inner work without await', () async { + final order = await runWithFinally(awaitInner: false); + await Future.delayed(const Duration(milliseconds: 30)); + expect(order.first, 'restore'); + expect(order.last, 'inner'); + }); + + test('restore runs after inner work with await', () async { + final order = await runWithFinally(awaitInner: true); + expect(order, ['inner', 'restore']); + }); + }); } diff --git a/modules/ensemble/test/wifi_action_test.dart b/modules/ensemble/test/wifi_action_test.dart new file mode 100644 index 000000000..6eb71c146 --- /dev/null +++ b/modules/ensemble/test/wifi_action_test.dart @@ -0,0 +1,39 @@ +import 'package:ensemble/action/wifi_action.dart'; +import 'package:ensemble/framework/action.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:yaml/yaml.dart'; + +void main() { + group('ConnectToWifiAction.fromYaml', () { + test('parses connect operation by default', () { + final action = ConnectToWifiAction.fromYaml(payload: { + 'ssid': 'MyNetwork', + 'password': 'secret', + }); + + expect(action.operation, WifiOperation.connect); + expect(action.ssid, 'MyNetwork'); + expect(action.password, 'secret'); + }); + + test('parses disconnect operation', () { + final action = ConnectToWifiAction.fromYaml(payload: { + 'operation': 'disconnect', + }); + + expect(action.operation, WifiOperation.disconnect); + }); + + test('parses optional callbacks', () { + final action = ConnectToWifiAction.fromYaml(payload: { + 'ssid': 'MyNetwork', + 'onError': { + 'showToast': {'message': 'failed'}, + }, + }); + + expect(action.onSuccess, isNull); + expect(action.onError, isA()); + }); + }); +}