This is an automated email from the ASF dual-hosted git repository.

GitToTheHub pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cordova-ios.git


The following commit(s) were added to refs/heads/master by this push:
     new bad09df9 fix(CDVStatusBarTests): wait for native exec (#1680)
bad09df9 is described below

commit bad09df990277d1d5817cb24a6f4e21fc0df222f
Author: Manuel Beck <[email protected]>
AuthorDate: Sat Aug 8 15:06:34 2026 +0200

    fix(CDVStatusBarTests): wait for native exec (#1680)
    
    - Sometimes actions will fail due to race conditions on the 
`testStatusBarBackgroundColor*`-methods. `evaluateJavaScript` completes once 
JavaScript has executed, but `cordova.exec(...)` queues the native 
`StatusBarInternal.setBackgroundColor` command asynchronously. The test then 
immediately reads `statusBarBackgroundColor`, potentially before the native 
command runs.
    
    Generated-By: GPT-5.5 Medium
    
    * test: add Cordova exec queue barrier helper
    
    - Add a reusable test helper that evaluates JavaScript and waits until 
Cordova exec calls queued by that JavaScript have reached native code.
    - StatusBar tests now use this helper before asserting native status bar 
state, avoiding races where evaluateJavaScript completes before the native 
StatusBarInternal command has run. Also register the deviceready expectation 
before loading the start page.
    
    Generated-By: ChatGPT 5.5
---
 tests/CordovaLibTests/CDVStatusBarTests.m | 77 ++++++++++++-------------------
 tests/CordovaLibTests/CDVTestHelpers.h    | 37 +++++++++++++++
 tests/CordovaLibTests/CDVTestHelpers.m    | 74 +++++++++++++++++++++++++++++
 3 files changed, 140 insertions(+), 48 deletions(-)

diff --git a/tests/CordovaLibTests/CDVStatusBarTests.m 
b/tests/CordovaLibTests/CDVStatusBarTests.m
index db670c73..7a48d4c7 100644
--- a/tests/CordovaLibTests/CDVStatusBarTests.m
+++ b/tests/CordovaLibTests/CDVStatusBarTests.m
@@ -42,6 +42,11 @@
     self.viewController = self.appDelegate.testViewController;
 
     self.plugin = (CDVWebViewEngine *)self.viewController.webViewEngine;
+    // StatusBar JavaScript APIs use cordova.exec to update native state. The
+    // shared test helper registers a no-op native plugin that lets each test
+    // wait until those exec calls have reached native code before asserting 
the
+    // view controller's statusBarBackgroundColor.
+    [self 
registerCordovaCommandQueueTestPluginForViewController:self.viewController];
 }
 
 - (void)tearDown {
@@ -52,17 +57,13 @@
 - (void) testStatusBarBackgroundColorNamedColor {
     CGFloat rgba[4] = {0.f, 0.f, 0.f, 0.f};
 
-    [self.viewController loadStartPage];
-
     XCTestExpectation *loadExpectation = [[XCTNSNotificationExpectation alloc] 
initWithName:CDVTestingDeviceReadyFired];
+    [self.viewController loadStartPage];
     [self waitForExpectations:@[loadExpectation] timeout:5];
 
-    __block XCTestExpectation *redExpectation = [self 
expectationWithDescription:@"red"];
-    [self.plugin 
evaluateJavaScript:@"window.statusbar.setBackgroundColor('red');" 
completionHandler:^(id _, NSError* error) {
-        XCTAssertNil(error);
-        [redExpectation fulfill];
-    }];
-    [self waitForExpectations:@[redExpectation] timeout:5];
+    [self evaluateJavaScript:@"window.statusbar.setBackgroundColor('red');"
+andWaitForCordovaCommandQueueWithWebViewEngine:self.plugin
+                     timeout:5];
 
     [self.viewController.statusBarBackgroundColor getRed:&rgba[0] 
green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];
     XCTAssertEqual(rgba[0], 1.f);
@@ -74,17 +75,13 @@
 - (void) testStatusBarBackgroundColor3HexColor {
     CGFloat rgba[4] = {0.f, 0.f, 0.f, 0.f};
 
-    [self.viewController loadStartPage];
-
     XCTestExpectation *loadExpectation = [[XCTNSNotificationExpectation alloc] 
initWithName:CDVTestingDeviceReadyFired];
+    [self.viewController loadStartPage];
     [self waitForExpectations:@[loadExpectation] timeout:5];
 
-    __block XCTestExpectation *redExpectation = [self 
expectationWithDescription:@"red"];
-    [self.plugin 
evaluateJavaScript:@"window.statusbar.setBackgroundColor('#f00');" 
completionHandler:^(id _, NSError* error) {
-        XCTAssertNil(error);
-        [redExpectation fulfill];
-    }];
-    [self waitForExpectations:@[redExpectation] timeout:5];
+    [self evaluateJavaScript:@"window.statusbar.setBackgroundColor('#f00');"
+andWaitForCordovaCommandQueueWithWebViewEngine:self.plugin
+                     timeout:5];
 
     [self.viewController.statusBarBackgroundColor getRed:&rgba[0] 
green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];
     XCTAssertEqual(rgba[0], 1.f);
@@ -96,17 +93,13 @@
 - (void) testStatusBarBackgroundColor6HexColor {
     CGFloat rgba[4] = {0.f, 0.f, 0.f, 0.f};
 
-    [self.viewController loadStartPage];
-
     XCTestExpectation *loadExpectation = [[XCTNSNotificationExpectation alloc] 
initWithName:CDVTestingDeviceReadyFired];
+    [self.viewController loadStartPage];
     [self waitForExpectations:@[loadExpectation] timeout:5];
 
-    __block XCTestExpectation *redExpectation = [self 
expectationWithDescription:@"red"];
-    [self.plugin 
evaluateJavaScript:@"window.statusbar.setBackgroundColor('#ff0000');" 
completionHandler:^(id _, NSError* error) {
-        XCTAssertNil(error);
-        [redExpectation fulfill];
-    }];
-    [self waitForExpectations:@[redExpectation] timeout:5];
+    [self evaluateJavaScript:@"window.statusbar.setBackgroundColor('#ff0000');"
+andWaitForCordovaCommandQueueWithWebViewEngine:self.plugin
+                     timeout:5];
 
     [self.viewController.statusBarBackgroundColor getRed:&rgba[0] 
green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];
     XCTAssertEqual(rgba[0], 1.f);
@@ -118,17 +111,13 @@
 - (void) testStatusBarBackgroundColor8HexColor {
     CGFloat rgba[4] = {0.f, 0.f, 0.f, 0.f};
 
-    [self.viewController loadStartPage];
-
     XCTestExpectation *loadExpectation = [[XCTNSNotificationExpectation alloc] 
initWithName:CDVTestingDeviceReadyFired];
+    [self.viewController loadStartPage];
     [self waitForExpectations:@[loadExpectation] timeout:5];
 
-    __block XCTestExpectation *redExpectation = [self 
expectationWithDescription:@"red"];
-    [self.plugin 
evaluateJavaScript:@"window.statusbar.setBackgroundColor('#ff000080');" 
completionHandler:^(id _, NSError* error) {
-        XCTAssertNil(error);
-        [redExpectation fulfill];
-    }];
-    [self waitForExpectations:@[redExpectation] timeout:5];
+    [self 
evaluateJavaScript:@"window.statusbar.setBackgroundColor('#ff000080');"
+andWaitForCordovaCommandQueueWithWebViewEngine:self.plugin
+                     timeout:5];
 
     [self.viewController.statusBarBackgroundColor getRed:&rgba[0] 
green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];
     XCTAssertEqual(rgba[0], 1.f);
@@ -140,17 +129,13 @@
 - (void) testStatusBarBackgroundColorRGB {
     CGFloat rgba[4] = {0.f, 0.f, 0.f, 0.f};
 
-    [self.viewController loadStartPage];
-
     XCTestExpectation *loadExpectation = [[XCTNSNotificationExpectation alloc] 
initWithName:CDVTestingDeviceReadyFired];
+    [self.viewController loadStartPage];
     [self waitForExpectations:@[loadExpectation] timeout:5];
 
-    __block XCTestExpectation *redExpectation = [self 
expectationWithDescription:@"red"];
-    [self.plugin 
evaluateJavaScript:@"window.statusbar.setBackgroundColor('rgb(255, 0, 0)');" 
completionHandler:^(id _, NSError* error) {
-        XCTAssertNil(error);
-        [redExpectation fulfill];
-    }];
-    [self waitForExpectations:@[redExpectation] timeout:5];
+    [self evaluateJavaScript:@"window.statusbar.setBackgroundColor('rgb(255, 
0, 0)');"
+andWaitForCordovaCommandQueueWithWebViewEngine:self.plugin
+                     timeout:5];
 
     [self.viewController.statusBarBackgroundColor getRed:&rgba[0] 
green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];
     XCTAssertEqual(rgba[0], 1.f);
@@ -162,17 +147,13 @@
 - (void) testStatusBarBackgroundColorRGBA {
     CGFloat rgba[4] = {0.f, 0.f, 0.f, 0.f};
 
-    [self.viewController loadStartPage];
-
     XCTestExpectation *loadExpectation = [[XCTNSNotificationExpectation alloc] 
initWithName:CDVTestingDeviceReadyFired];
+    [self.viewController loadStartPage];
     [self waitForExpectations:@[loadExpectation] timeout:5];
 
-    __block XCTestExpectation *redExpectation = [self 
expectationWithDescription:@"red"];
-    [self.plugin 
evaluateJavaScript:@"window.statusbar.setBackgroundColor('rgba(255, 0, 0, 
0.25)');" completionHandler:^(id _, NSError* error) {
-        XCTAssertNil(error);
-        [redExpectation fulfill];
-    }];
-    [self waitForExpectations:@[redExpectation] timeout:5];
+    [self evaluateJavaScript:@"window.statusbar.setBackgroundColor('rgba(255, 
0, 0, 0.25)');"
+andWaitForCordovaCommandQueueWithWebViewEngine:self.plugin
+                     timeout:5];
 
     [self.viewController.statusBarBackgroundColor getRed:&rgba[0] 
green:&rgba[1] blue:&rgba[2] alpha:&rgba[3]];
     XCTAssertEqual(rgba[0], 1.f);
diff --git a/tests/CordovaLibTests/CDVTestHelpers.h 
b/tests/CordovaLibTests/CDVTestHelpers.h
index 42acdf2e..9f9bb5a0 100644
--- a/tests/CordovaLibTests/CDVTestHelpers.h
+++ b/tests/CordovaLibTests/CDVTestHelpers.h
@@ -22,8 +22,45 @@
 
 extern const NSNotificationName CDVTestingDeviceReadyFired;
 
+@class CDVViewController;
+@protocol CDVWebViewEngineProtocol;
+
 @interface TestNavigationDelegate : NSObject <WKNavigationDelegate>
 @property (nonatomic, copy) void (^didFinishNavigation)(WKWebView *, 
WKNavigation *);
 
 - (void)waitForDidFinishNavigation:(XCTestExpectation *)expectation;
 @end
+
+@interface XCTestCase (CordovaTestHelpers)
+
+/**
+ * Registers the test-only Cordova command queue barrier plugin.
+ *
+ * Some tests execute JavaScript that calls Cordova APIs. Those APIs often call
+ * cordova.exec, which sends work from JavaScript to native code 
asynchronously.
+ * XCTest should not assert native state until that native work has actually
+ * been handled. Register this plugin before using
+ * evaluateJavaScript:andWaitForCordovaCommandQueueWithWebViewEngine:timeout:.
+ */
+- 
(void)registerCordovaCommandQueueTestPluginForViewController:(CDVViewController 
*)viewController;
+
+/**
+ * Evaluates JavaScript and waits until all cordova.exec calls queued by that
+ * JavaScript have reached native code.
+ *
+ * WKWebView's evaluateJavaScript completion handler only tells us that the
+ * JavaScript expression finished evaluating. It does not guarantee that native
+ * plugin commands triggered by cordova.exec have already run. This helper
+ * appends a test-only no-op exec call after the provided JavaScript and waits
+ * until that no-op command is received natively. Since Cordova processes exec
+ * messages in order, receiving the no-op command means earlier exec calls from
+ * the same JavaScript evaluation have already reached native code.
+ *
+ * This is a synchronization helper for tests that need to read native state
+ * after invoking JavaScript APIs backed by Cordova plugins.
+ */
+- (void)evaluateJavaScript:(NSString *)javaScript
+andWaitForCordovaCommandQueueWithWebViewEngine:(id 
<CDVWebViewEngineProtocol>)webViewEngine
+                   timeout:(NSTimeInterval)timeout;
+
+@end
diff --git a/tests/CordovaLibTests/CDVTestHelpers.m 
b/tests/CordovaLibTests/CDVTestHelpers.m
index d6b1c06b..f7fe5d4c 100644
--- a/tests/CordovaLibTests/CDVTestHelpers.m
+++ b/tests/CordovaLibTests/CDVTestHelpers.m
@@ -18,8 +18,40 @@
 */
 
 #import "CDVTestHelpers.h"
+#import <Cordova/Cordova.h>
 
 const NSNotificationName CDVTestingDeviceReadyFired = 
@"CDVTestingDeviceReadyFired";
+static NSNotificationName const CDVTestingCommandQueueBarrierReached = 
@"CDVTestingCommandQueueBarrierReached";
+static NSString * const CDVTestingCommandQueuePluginName = 
@"CordovaTestingCommandQueue";
+
+/**
+ * Test-only Cordova plugin used as a synchronization barrier.
+ *
+ * The production WKWebView bridge delivers cordova.exec calls to native code
+ * asynchronously. A test that evaluates JavaScript and immediately checks 
native
+ * state can therefore race with the native plugin command that JavaScript
+ * enqueued. This plugin gives tests a known command that can be appended after
+ * the JavaScript under test. When this command reaches native code, all 
earlier
+ * exec commands from the same JavaScript evaluation have also reached native
+ * code.
+ *
+ * The plugin intentionally performs no production behavior. It only posts an
+ * XCTest-visible notification so the test can continue.
+ */
+@interface CDVTestingCommandQueue : CDVPlugin
+
+- (void)barrier:(CDVInvokedUrlCommand *)command;
+
+@end
+
+@implementation CDVTestingCommandQueue
+
+- (void)barrier:(CDVInvokedUrlCommand *)command
+{
+    [[NSNotificationCenter defaultCenter] 
postNotificationName:CDVTestingCommandQueueBarrierReached object:self];
+}
+
+@end
 
 @implementation TestNavigationDelegate
 - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation 
*)navigation
@@ -39,3 +71,45 @@ const NSNotificationName CDVTestingDeviceReadyFired = 
@"CDVTestingDeviceReadyFir
     };
 }
 @end
+
+@implementation XCTestCase (CordovaTestHelpers)
+
+- 
(void)registerCordovaCommandQueueTestPluginForViewController:(CDVViewController 
*)viewController
+{
+    // Register the barrier plugin with the Cordova plugin manager used by this
+    // test view controller. The JavaScript side refers to this plugin by
+    // CDVTestingCommandQueuePluginName when it appends the barrier exec call.
+    [viewController registerPlugin:[[CDVTestingCommandQueue alloc] init] 
withPluginName:CDVTestingCommandQueuePluginName];
+}
+
+- (void)evaluateJavaScript:(NSString *)javaScript
+andWaitForCordovaCommandQueueWithWebViewEngine:(id 
<CDVWebViewEngineProtocol>)webViewEngine
+                   timeout:(NSTimeInterval)timeout
+{
+    XCTestExpectation *commandQueueExpectation = 
[[XCTNSNotificationExpectation alloc] 
initWithName:CDVTestingCommandQueueBarrierReached];
+    XCTestExpectation *javaScriptExpectation = [self 
expectationWithDescription:@"JavaScript evaluation"];
+    // The barrier must be appended to the same JavaScript evaluation as the 
code
+    // under test. If it were evaluated in a separate evaluateJavaScript call,
+    // WebKit could complete or schedule the second evaluation before the first
+    // evaluation's cordova.exec message had been delivered to native code. By
+    // putting both exec calls in one JavaScript evaluation, JavaScript 
enqueues
+    // them in order and the native barrier becomes a reliable FIFO checkpoint.
+    NSString *barrier = [NSString stringWithFormat:@"\ncordova.exec(null, 
null, '%@', 'barrier', []);", CDVTestingCommandQueuePluginName];
+    NSString *javaScriptWithQueueBarrier = [javaScript 
stringByAppendingString:barrier];
+
+    // Wait for both pieces:
+    // 1. evaluateJavaScript completed without a WebKit/JavaScript error.
+    // 2. The appended Cordova barrier command reached native code.
+    //
+    // Only after both are true is it safe for the test to assert native state
+    // that should have been changed by a synchronous plugin command.
+    [webViewEngine evaluateJavaScript:javaScriptWithQueueBarrier
+                    completionHandler:^(id _, NSError* error) {
+        XCTAssertNil(error);
+        [javaScriptExpectation fulfill];
+    }];
+
+    [self waitForExpectations:@[javaScriptExpectation, 
commandQueueExpectation] timeout:timeout];
+}
+
+@end


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to