diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java
index a749a31e14..e4f37534ec 100644
--- a/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java
+++ b/Ports/JavaSE/src/com/codename1/impl/javase/BuildHintSchemaDefaults.java
@@ -58,9 +58,12 @@ static void register() {
// Group.
set("{{@nativeTheme}}.label", "Native Theme");
set("{{@nativeTheme}}.description",
- "Controls the Codename One look & feel on iOS and Android. "
- + "Modern themes are generated from CSS under native-themes/; "
- + "legacy themes remain selectable via the values below.");
+ "Controls the Codename One look & feel on iOS, Android, and "
+ + "the JavaScript port (browser OS auto-detection: iOS/Mac "
+ + "browsers get the iOS theme, everything else gets the "
+ + "Android theme). Modern themes are generated from CSS "
+ + "under native-themes/; legacy themes remain selectable "
+ + "via the values below.");
// Cross-platform meta hint.
set("{{#nativeTheme#nativeTheme}}.label", "Shared override");
diff --git a/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java b/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java
index 7238e67376..2c467d6656 100644
--- a/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java
+++ b/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/HTML5Implementation.java
@@ -2816,60 +2816,27 @@ public void setHeight(HTMLCanvasElement canvas, int canvasHeight) {
@Override
public void installNativeTheme(){
try {
- // Prefer the modern native theme when explicitly requested via
- // ios.themeMode / and.themeMode (legacy alias: cn1.androidTheme)
- // / nativeTheme (legacy alias: cn1.nativeTheme) /
- // javascript.native.theme. If no hint is set we keep the
- // pre-existing JS-port default (iOS 7 / Holo Light) since the JS
- // bundle may not include the modern .res files
- // (scripts/build-native-themes.sh has to have mirrored them
- // before the JS bundle was produced).
- String defaultTheme = isAndroid_() ? "/android_holo_light.res" : "/iOS7Theme.res";
- Display d = Display.getInstance();
- String iosMode = d.getProperty("ios.themeMode", null);
- String androidMode = d.getProperty("and.themeMode",
- d.getProperty("cn1.androidTheme", null));
- String shared = d.getProperty("nativeTheme",
- d.getProperty("cn1.nativeTheme", null));
- if (isAndroid_()) {
- if (androidMode == null && shared != null) {
- if ("modern".equalsIgnoreCase(shared)) {
- androidMode = "material";
- } else if ("legacy".equalsIgnoreCase(shared)) {
- androidMode = "hololight";
- }
- }
- if (androidMode != null) {
- androidMode = androidMode.toLowerCase();
- if ("material".equals(androidMode) || "modern".equals(androidMode) || "auto".equals(androidMode)) {
- defaultTheme = "/AndroidMaterialTheme.res";
- } else if ("legacy".equals(androidMode)) {
- defaultTheme = "/androidTheme.res";
- } else if ("hololight".equals(androidMode) || "holo".equals(androidMode)) {
- defaultTheme = "/android_holo_light.res";
- }
- }
- } else {
- if (iosMode == null && shared != null) {
- if ("modern".equalsIgnoreCase(shared)) {
- iosMode = "modern";
- } else if ("legacy".equalsIgnoreCase(shared)) {
- iosMode = "ios7";
- }
- }
- if (iosMode != null) {
- iosMode = iosMode.toLowerCase();
- if ("modern".equals(iosMode) || "liquid".equals(iosMode) || "auto".equals(iosMode)) {
- defaultTheme = "/iOSModernTheme.res";
- } else if ("legacy".equals(iosMode) || "iphone".equals(iosMode)) {
- defaultTheme = "/iPhoneTheme.res";
- }
- }
- }
- String nativeTheme = Display.getInstance().getProperty("javascript.native.theme", defaultTheme);
+ // Pick the .res to load based on the build hints and the
+ // detected browser OS. The JS-port default stays on the
+ // pre-existing legacy theme (Holo Light when the user agent
+ // is Android, iOS 7 on everything else) so legacy
+ // screenshot baselines remain comparable. Apps that want
+ // the modern Liquid Glass / Material 3 theme can opt in
+ // via ios.themeMode / and.themeMode / nativeTheme (legacy
+ // aliases: cn1.androidTheme / cn1.nativeTheme) /
+ // javascript.native.theme, including the new "auto" value
+ // that picks iOS modern for iOS/Mac browsers and Material
+ // 3 elsewhere.
+ String resolved = resolveNativeThemeResource();
+ // Publish the detected modern-theme resource so screenshot
+ // tests (DualAppearanceBaseTest) can install the same
+ // platform-appropriate theme on the JS port without
+ // duplicating the OS-detection logic.
+ String modern = isIOSLikeBrowser() ? "/iOSModernTheme.res" : "/AndroidMaterialTheme.res";
+ Display.getInstance().setProperty("cn1.modernThemeResource", modern);
Resources r;
try {
- r = Resources.open(nativeTheme);
+ r = Resources.open(resolved);
} catch (Throwable notFound) {
// Fall back to the legacy theme if the chosen .res isn't in
// the JS bundle (partial build, missing mirror step, etc.).
@@ -2890,6 +2857,104 @@ public void installNativeTheme(){
return;
}
+ /**
+ * iOS/Mac browsers should pick up the iOS Liquid Glass theme;
+ * every other browser (Android, Windows, Linux, Chrome OS) gets
+ * the Android Material 3 theme. The legacy default split was
+ * Android vs. "everything else falls back to iOS"; for the modern
+ * native theme the more useful split is iOS-family vs. everyone
+ * else because Windows and Linux desktops match Material 3 better
+ * than Liquid Glass.
+ */
+ private static boolean isIOSLikeBrowser() {
+ return isIOS() || isMac();
+ }
+
+ /**
+ * Resolves the native-theme .res path from build hints + detected
+ * browser OS. Recognised hints (highest to lowest precedence):
+ * - {@code javascript.native.theme}: explicit res path override.
+ * - {@code ios.themeMode}: auto/modern/liquid/ios7/flat/legacy/
+ * iphone. Applied to iOS/Mac browsers only.
+ * - {@code and.themeMode}: auto/material/modern/hololight/holo/
+ * legacy. Applied when the browser is not iOS/Mac. Alias:
+ * {@code cn1.androidTheme}.
+ * - {@code nativeTheme}: modern/auto/legacy. Maps onto the iOS
+ * or Android branch based on the detected browser OS. Alias:
+ * {@code cn1.nativeTheme}. The new {@code auto} value triggers
+ * OS-based selection: iOS/Mac browsers get the Liquid Glass
+ * theme, everything else gets Material 3.
+ *
+ *
With no hint set, the JS port keeps the pre-existing default
+ * (Android Holo Light when the user agent is Android, iOS 7
+ * everywhere else - identical to the historical legacy split) so
+ * existing JS screenshot baselines remain comparable. Apps that
+ * want the modern Liquid Glass / Material 3 theme can opt in by
+ * setting {@code nativeTheme=auto} or {@code nativeTheme=modern}
+ * (or the platform-specific {@code ios.themeMode} /
+ * {@code and.themeMode}). The {@code cn1.modernThemeResource}
+ * runtime property published by {@link #installNativeTheme()}
+ * always points at the OS-appropriate modern .res so screenshot
+ * tests can install it regardless of the configured default.
+ */
+ private static String resolveNativeThemeResource() {
+ Display d = Display.getInstance();
+ String explicit = d.getProperty("javascript.native.theme", null);
+ if (explicit != null && explicit.length() > 0) {
+ return explicit;
+ }
+ String shared = d.getProperty("nativeTheme", d.getProperty("cn1.nativeTheme", null));
+ // Auto-detected branch chooses Liquid Glass on iOS/Mac and
+ // Material 3 elsewhere. Used for any "modern"/"auto" path.
+ boolean iosLike = isIOSLikeBrowser();
+ if (iosLike) {
+ String iosMode = d.getProperty("ios.themeMode", null);
+ if (iosMode == null && shared != null) {
+ if ("modern".equalsIgnoreCase(shared) || "auto".equalsIgnoreCase(shared)) {
+ iosMode = "modern";
+ } else if ("legacy".equalsIgnoreCase(shared)) {
+ iosMode = "ios7";
+ }
+ }
+ if (iosMode != null) {
+ iosMode = iosMode.toLowerCase();
+ if ("legacy".equals(iosMode) || "iphone".equals(iosMode)) {
+ return "/iPhoneTheme.res";
+ }
+ if ("ios7".equals(iosMode) || "flat".equals(iosMode)) {
+ return "/iOS7Theme.res";
+ }
+ // modern / liquid / auto / anything else -> modern theme
+ return "/iOSModernTheme.res";
+ }
+ // No iOS hint - keep the pre-existing JS-port default.
+ return "/iOS7Theme.res";
+ }
+ String androidMode = d.getProperty("and.themeMode", d.getProperty("cn1.androidTheme", null));
+ if (androidMode == null && shared != null) {
+ if ("modern".equalsIgnoreCase(shared) || "auto".equalsIgnoreCase(shared)) {
+ androidMode = "material";
+ } else if ("legacy".equalsIgnoreCase(shared)) {
+ androidMode = "hololight";
+ }
+ }
+ if (androidMode != null) {
+ androidMode = androidMode.toLowerCase();
+ if ("legacy".equals(androidMode)) {
+ return "/androidTheme.res";
+ }
+ if ("hololight".equals(androidMode) || "holo".equals(androidMode)) {
+ return "/android_holo_light.res";
+ }
+ // material / modern / auto / anything else -> modern theme
+ return "/AndroidMaterialTheme.res";
+ }
+ // No Android hint - keep the pre-existing JS-port default,
+ // which routes Android user agents to Holo Light and every
+ // other non-iOS browser (Linux/Windows/Chrome OS) to iOS 7.
+ return isAndroid_() ? "/android_holo_light.res" : "/iOS7Theme.res";
+ }
+
@Override
public boolean hasNativeTheme() {
return true;
diff --git a/Ports/JavaScriptPort/src/main/webapp/port.js b/Ports/JavaScriptPort/src/main/webapp/port.js
index c92c98a82b..55df041acd 100644
--- a/Ports/JavaScriptPort/src/main/webapp/port.js
+++ b/Ports/JavaScriptPort/src/main/webapp/port.js
@@ -3214,20 +3214,15 @@ const cn1ssForcedTimeoutTestClasses = Object.freeze({
// emitChannel hijack — see matching entry in cn1ssForcedTimeoutTestNames below.
"com_codenameone_examples_hellocodenameone_tests_ChatInputScreenshotTest": "chatInputEmitHijack",
"com_codenameone_examples_hellocodenameone_tests_ChatViewScreenshotTest": "chatViewEmitHijack",
- "com_codenameone_examples_hellocodenameone_tests_ButtonThemeScreenshotTest": "themeScreenshot",
- "com_codenameone_examples_hellocodenameone_tests_TextFieldThemeScreenshotTest": "themeScreenshot",
- "com_codenameone_examples_hellocodenameone_tests_CheckBoxRadioThemeScreenshotTest": "themeScreenshot",
- "com_codenameone_examples_hellocodenameone_tests_SwitchThemeScreenshotTest": "themeScreenshot",
- "com_codenameone_examples_hellocodenameone_tests_PickerThemeScreenshotTest": "themeScreenshot",
- "com_codenameone_examples_hellocodenameone_tests_ToolbarThemeScreenshotTest": "themeScreenshot",
- "com_codenameone_examples_hellocodenameone_tests_TabsThemeScreenshotTest": "themeScreenshot",
- "com_codenameone_examples_hellocodenameone_tests_MultiButtonThemeScreenshotTest": "themeScreenshot",
- "com_codenameone_examples_hellocodenameone_tests_ListThemeScreenshotTest": "themeScreenshot",
- "com_codenameone_examples_hellocodenameone_tests_DialogThemeScreenshotTest": "themeScreenshot",
- "com_codenameone_examples_hellocodenameone_tests_FloatingActionButtonThemeScreenshotTest": "themeScreenshot",
- "com_codenameone_examples_hellocodenameone_tests_SpanLabelThemeScreenshotTest": "themeScreenshot",
- "com_codenameone_examples_hellocodenameone_tests_DarkLightShowcaseThemeScreenshotTest": "themeScreenshot",
- "com_codenameone_examples_hellocodenameone_tests_PaletteOverrideThemeScreenshotTest": "themeScreenshot",
+ // The 14 *ThemeScreenshotTest entries that used to live here were
+ // unparked when the JS port started bundling the modern native
+ // theme resources (iOSModernTheme.res / AndroidMaterialTheme.res
+ // mirrored into webapp/assets/ by scripts/build-native-themes.sh).
+ // DualAppearanceBaseTest now installs the OS-appropriate modern
+ // theme via the cn1.modernThemeResource Display property that
+ // HTML5Implementation publishes during installNativeTheme(); see
+ // the matching cn1ssForcedTimeoutTestNames map below for the
+ // short-name list that was un-parked in tandem.
// jsChunkDrop block removed for the graphics grid, KotlinUiTest,
// MainScreenScreenshotTest, the Tabs / ImageViewer / TextArea /
// ToastBar / picker tests, and ChartLine -- the chunk emitter bug
@@ -3348,20 +3343,10 @@ const cn1ssForcedTimeoutTestNames = Object.freeze({
// the JS-port emit path (separate investigation).
"ChatInputScreenshotTest": "chatInputEmitHijack",
"ChatViewScreenshotTest": "chatViewEmitHijack",
- "ButtonThemeScreenshotTest": "themeScreenshot",
- "TextFieldThemeScreenshotTest": "themeScreenshot",
- "CheckBoxRadioThemeScreenshotTest": "themeScreenshot",
- "SwitchThemeScreenshotTest": "themeScreenshot",
- "PickerThemeScreenshotTest": "themeScreenshot",
- "ToolbarThemeScreenshotTest": "themeScreenshot",
- "TabsThemeScreenshotTest": "themeScreenshot",
- "MultiButtonThemeScreenshotTest": "themeScreenshot",
- "ListThemeScreenshotTest": "themeScreenshot",
- "DialogThemeScreenshotTest": "themeScreenshot",
- "FloatingActionButtonThemeScreenshotTest": "themeScreenshot",
- "SpanLabelThemeScreenshotTest": "themeScreenshot",
- "DarkLightShowcaseThemeScreenshotTest": "themeScreenshot",
- "PaletteOverrideThemeScreenshotTest": "themeScreenshot",
+ // The 14 *ThemeScreenshotTest short-name entries were un-parked
+ // alongside the fully-qualified-class entries in
+ // cn1ssForcedTimeoutTestClasses above when the modern native
+ // theme resources started shipping in the JS port bundle.
// See the matching comment in cn1ssForcedTimeoutTestClasses above:
// the jsChunkDrop short-name entries (KotlinUiTest,
// MainScreenScreenshotTest, the ImageViewer / Tabs / TextArea /
diff --git a/maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java b/maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java
index c105f365be..d52142e5a7 100644
--- a/maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java
+++ b/maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java
@@ -6218,6 +6218,23 @@ public com.codename1.ui.plaf.Border getThemeBorder(Map style
if (cn1BackgroundType != null && usesRoundBorder(styles)) {
return createRoundBorder(styles);
}
+ // Prefer RoundRectBorder for the simple border-radius case
+ // (no border-image, no compound per-side borders). The JS
+ // port's CSSBorder.paintBorderBackground calls
+ // g.fillShape(p) directly against the main canvas, which
+ // doesn't actually render on the cooperative-scheduler
+ // worker-side bridge (visible symptom: Dialog / TextField /
+ // ChatBubble UIIDs show no rounded background, only their
+ // children render). RoundRectBorder.paintBorderBackground
+ // routes through createTargetImage + drawImage on the same
+ // path that already works for cn1-pill-border (Button), so
+ // switching dispatch fixes Dialog without changing iOS /
+ // Android pixels (RoundRectBorder produces a visually-
+ // equivalent rounded rect there).
+ if (b.canBeAchievedWithRoundRectBorder(styles) && b.hasBorderRadius()
+ && !b.hasBorderImage() && !b.hasUnequalBorders()) {
+ return createRoundRectBorder(styles);
+ }
if (b.canBeAchievedWithCSSBorder(styles) && (b.hasBorderImage() || b.hasUnequalBorders() || b.hasBorderRadius()) ) {
return createCSSBorder(styles);
}
diff --git a/scripts/build-javascript-port-hellocodenameone.sh b/scripts/build-javascript-port-hellocodenameone.sh
index 8869234b55..3e82593f33 100755
--- a/scripts/build-javascript-port-hellocodenameone.sh
+++ b/scripts/build-javascript-port-hellocodenameone.sh
@@ -64,6 +64,22 @@ if [ "${SKIP_MAVEN_BUILD:-0}" != "1" ] && [ "${SKIP_PARPARVM_BUILD:-0}" != "1" ]
mvn -B -f "$REPO_ROOT/maven/pom.xml" -pl parparvm -am -DskipTests -Dmaven.javadoc.skip=true package
fi
+# Mirror the modern native themes (iOSModernTheme.res /
+# AndroidMaterialTheme.res) into Ports/JavaScriptPort/src/main/webapp/
+# assets so the bundle picks them up when JavascriptBundleWriter copies
+# webapp/assets/* into the served output. The mirror files are
+# gitignored build artefacts, so without this step a fresh checkout
+# would silently fall back to iOS7Theme.res / android_holo_light.res
+# at runtime.
+if [ "${SKIP_NATIVE_THEMES_BUILD:-0}" != "1" ]; then
+ if [ -x "$REPO_ROOT/scripts/build-native-themes.sh" ]; then
+ bj_log "Compiling native themes (iOS Modern / Android Material) for JS bundle"
+ "$REPO_ROOT/scripts/build-native-themes.sh"
+ else
+ bj_log "WARNING: scripts/build-native-themes.sh missing - modern themes won't be in bundle"
+ fi
+fi
+
if [ "${SKIP_MAVEN_BUILD:-0}" != "1" ] && [ "${SKIP_COMMON_BUILD:-0}" != "1" ]; then
bj_log "Building HelloCodenameOne common module and compile-scope dependencies"
mkdir -p "$HOME/.codenameone"
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java
index 9fc609a146..e1da43daf0 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java
@@ -55,27 +55,35 @@
public final class Cn1ssDeviceRunner extends DeviceRunner {
- // Per-test deadline cap. On the JavaScript port the overall CI browser
- // lifetime is only ~150s, so a stuck test mustn't eat the whole budget;
- // the tight 10s cap kept the suite from being blocked by the rare hung
- // form (e.g. KotlinUiTest's missing cn1lib). iOS / Android / JavaSE
- // run with much longer wall budgets so a per-test cap of 30s is safe -
- // it only matters for genuinely stuck tests, and 30s leaves headroom
- // for theme captures whose chunked-log emission is rate-limited (Android
- // throttles 500-byte chunks at 30ms each to keep logcat from dropping
- // lines, so a 60KB PNG plus its preview takes ~6s per appearance, and
- // a dual-appearance test like SpanLabelTheme legitimately needs ~12s
- // even on a healthy device). The 30s figure also covers the iOS Metal
- // port's mutable rendering, which allocates a temp UIImage + MTLTexture
- // per round-rect / arc / gradient call (the CG-rasterise-then-DrawImage
- // bridge), so FillRoundRect / DrawRoundRect and DrawImage on slow CI
- // simulators legitimately blow past the 10s budget while still making
- // forward progress.
+ // Per-test deadline cap. The 10s HTML5 default keeps already-
+ // hanging tests (LightweightPickerButtons, ToastBarTopPosition
+ // hitting the canvasContextWipe noCanvas path) from eating the
+ // 1740s suite-level budget. DualAppearanceBaseTest subclasses
+ // override this via {@link BaseTest#getTimeoutMs()} because they
+ // run light + dark phases serially and each phase pays
+ // registerReadyCallback's 1500ms UITimer + wait_for_ui_settle
+ // (up to ~800ms) + capture/encode + chunked emit, so the
+ // bytecode-translated path easily clears 10s on shared GHA
+ // runners. At 10s the dark phase's emit fired AFTER the test had
+ // already timed out and the runner had advanced to the next test
+ // - the late dark emit then captured whatever form happened to
+ // be on the canvas at that moment (visible symptom:
+ // ToolbarTheme_dark.png showed "TabsTheme / light"). iOS /
+ // Android / JavaSE keep their wider 30s cap because they don't
+ // share the JS port's canvas-hang failure mode.
private static final int TEST_TIMEOUT_MS_HTML5 = 10000;
private static final int TEST_TIMEOUT_MS_NATIVE = 30000;
private static final int TEST_POLL_INTERVAL_MS = 50;
- private static int testTimeoutMs() {
+ private static int testTimeoutMs(BaseTest testClass) {
+ // DualAppearanceBaseTest needs more wall time on HTML5 (light + dark
+ // phases serially, each paying registerReadyCallback's 1500ms + settle
+ // + capture). Other tests stay at the tighter HTML5 default so a hung
+ // test doesn't eat the suite-level budget.
+ if ("HTML5".equals(Display.getInstance().getPlatformName())
+ && testClass instanceof DualAppearanceBaseTest) {
+ return TEST_TIMEOUT_MS_NATIVE;
+ }
return "HTML5".equals(Display.getInstance().getPlatformName())
? TEST_TIMEOUT_MS_HTML5
: TEST_TIMEOUT_MS_NATIVE;
@@ -289,7 +297,7 @@ private void runNextTest(int index) {
logThrowable("runTest:" + testName, t);
testClass.fail(String.valueOf(t));
}
- awaitTestCompletion(index, testClass, testName, System.currentTimeMillis() + testTimeoutMs());
+ awaitTestCompletion(index, testClass, testName, System.currentTimeMillis() + testTimeoutMs(testClass));
});
}
@@ -306,7 +314,6 @@ private boolean shouldForceTimeoutInHtml5(String testName) {
// the 300s end-marker deadline. Keep all skip lookups inline to avoid
// triggering the same static-init failure path.
return isJsSkippedNativeTest(testName)
- || isJsSkippedThemeTest(testName)
|| isJsSkippedAnimationTest(testName)
|| isJsSkippedScreenshotTest(testName);
}
@@ -327,31 +334,6 @@ private static boolean isJsSkippedNativeTest(String testName) {
|| "CryptoApiTest".equals(testName);
}
- private static boolean isJsSkippedThemeTest(String testName) {
- // The native-theme fidelity tests (each emits a light+dark PNG pair)
- // matter for iOS/Android/JavaSE where the user actually looks at
- // visual output. The JS port run has a tight 150s browser-lifetime
- // budget that doesn't accommodate another 13 x 2 captures; skip them
- // here. Re-enable selectively when we move the JS port to a
- // longer-lived harness.
- return "ButtonThemeScreenshotTest".equals(testName)
- || "TextFieldThemeScreenshotTest".equals(testName)
- || "CheckBoxRadioThemeScreenshotTest".equals(testName)
- || "SwitchThemeScreenshotTest".equals(testName)
- || "PickerThemeScreenshotTest".equals(testName)
- || "ToolbarThemeScreenshotTest".equals(testName)
- || "TabsThemeScreenshotTest".equals(testName)
- || "MultiButtonThemeScreenshotTest".equals(testName)
- || "ListThemeScreenshotTest".equals(testName)
- || "DialogThemeScreenshotTest".equals(testName)
- || "FloatingActionButtonThemeScreenshotTest".equals(testName)
- || "SpanLabelThemeScreenshotTest".equals(testName)
- || "DarkLightShowcaseThemeScreenshotTest".equals(testName)
- || "PaletteOverrideThemeScreenshotTest".equals(testName)
- || "CssGradientsScreenshotTest".equals(testName)
- || "CssFilterBlurScreenshotTest".equals(testName);
- }
-
private static boolean isJsSkippedAnimationTest(String testName) {
// Animation grid tests render six full-form frames each. They exceed
// the JS port's 150s browser-lifetime budget and the value is already
diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DualAppearanceBaseTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DualAppearanceBaseTest.java
index a37fdee6fa..3cd8bebf1b 100644
--- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DualAppearanceBaseTest.java
+++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/DualAppearanceBaseTest.java
@@ -101,6 +101,23 @@ protected final void annotateComponent(Component c, String legend) {
annotations.add(new Annotation(c, legend));
}
+ /// Gates {@link #done()} until {@link #finish()} runs. The JS-port
+ /// emit fallback (`cn1ssEmitCurrentFormScreenshotDom` in port.js)
+ /// force-calls done() on the active test after the per-emit
+ /// completion runnable returns. For a single-phase test that's
+ /// the safety net that finalises the test; for DualAppearance
+ /// the light-phase completion only async-kicks-off the dark
+ /// phase (form.show() returns before paint), so the force-done
+ /// would finalise the test before the dark capture fires - the
+ /// runner then advances to the next test, and the late dark
+ /// emit captures whatever form happens to be on the canvas at
+ /// that moment (visible symptom: ListTheme_dark.png showed
+ /// "DialogTheme / light"; PickerTheme_dark.png showed Toolbar;
+ /// 7 of 16 modern-theme tests produced no captures at all
+ /// because they sat in the gap behind the polluting late emit).
+ /// finish() flips this gate so the natural done() chain works.
+ private volatile boolean bothPhasesComplete;
+
@Override
public boolean runTest() {
installModernThemeIfRequested();
@@ -108,6 +125,18 @@ public boolean runTest() {
return true;
}
+ @Override
+ protected synchronized void done() {
+ if (!bothPhasesComplete) {
+ // Premature done() call (e.g. JS-port force-done after the
+ // light emit's completion runnable returned). Stay
+ // not-done so the test runner keeps polling until the
+ // dark phase finishes and finish() flips the gate below.
+ return;
+ }
+ super.done();
+ }
+
private void runAppearance(boolean dark, final String suffix, final Runnable next) {
Display.getInstance().setDarkMode(dark);
// UIManager caches resolved Style objects per UIID; without this call
@@ -240,6 +269,9 @@ private void finish() {
UIManager.initFirstTheme("/theme");
}
UIManager.getInstance().refreshTheme();
+ // Lift the gate before calling done() so the overridden
+ // done() above lets the call through this time.
+ bothPhasesComplete = true;
done();
}
@@ -308,6 +340,16 @@ private String pickModernThemeResource() {
if ("and".equals(platform)) {
return "/AndroidMaterialTheme.res";
}
+ // The JavaScript port reports its platform as "HTML5"; it
+ // publishes ``cn1.modernThemeResource`` from HTML5Implementation's
+ // installNativeTheme() based on the detected browser OS
+ // (iOS/Mac -> iOSModernTheme.res, anything else ->
+ // AndroidMaterialTheme.res), which is the same selection
+ // logic this test would otherwise have to duplicate.
+ String published = Display.getInstance().getProperty("cn1.modernThemeResource", null);
+ if (published != null && published.length() > 0) {
+ return published;
+ }
return null;
}
diff --git a/scripts/javascript/screenshots/ButtonTheme_dark.png b/scripts/javascript/screenshots/ButtonTheme_dark.png
new file mode 100644
index 0000000000..d904d10cce
Binary files /dev/null and b/scripts/javascript/screenshots/ButtonTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/ButtonTheme_light.png b/scripts/javascript/screenshots/ButtonTheme_light.png
new file mode 100644
index 0000000000..42c8c49e9e
Binary files /dev/null and b/scripts/javascript/screenshots/ButtonTheme_light.png differ
diff --git a/scripts/javascript/screenshots/CheckBoxRadioTheme_dark.png b/scripts/javascript/screenshots/CheckBoxRadioTheme_dark.png
new file mode 100644
index 0000000000..b8803b9c90
Binary files /dev/null and b/scripts/javascript/screenshots/CheckBoxRadioTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/CheckBoxRadioTheme_light.png b/scripts/javascript/screenshots/CheckBoxRadioTheme_light.png
new file mode 100644
index 0000000000..a813bdd446
Binary files /dev/null and b/scripts/javascript/screenshots/CheckBoxRadioTheme_light.png differ
diff --git a/scripts/javascript/screenshots/DialogTheme_dark.png b/scripts/javascript/screenshots/DialogTheme_dark.png
new file mode 100644
index 0000000000..0ff95d7827
Binary files /dev/null and b/scripts/javascript/screenshots/DialogTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/DialogTheme_light.png b/scripts/javascript/screenshots/DialogTheme_light.png
new file mode 100644
index 0000000000..85909f6c1b
Binary files /dev/null and b/scripts/javascript/screenshots/DialogTheme_light.png differ
diff --git a/scripts/javascript/screenshots/FloatingActionButtonTheme_dark.png b/scripts/javascript/screenshots/FloatingActionButtonTheme_dark.png
new file mode 100644
index 0000000000..56882b0872
Binary files /dev/null and b/scripts/javascript/screenshots/FloatingActionButtonTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/FloatingActionButtonTheme_light.png b/scripts/javascript/screenshots/FloatingActionButtonTheme_light.png
new file mode 100644
index 0000000000..5c0eb2d1d8
Binary files /dev/null and b/scripts/javascript/screenshots/FloatingActionButtonTheme_light.png differ
diff --git a/scripts/javascript/screenshots/ListTheme_dark.png b/scripts/javascript/screenshots/ListTheme_dark.png
new file mode 100644
index 0000000000..8e53ff1698
Binary files /dev/null and b/scripts/javascript/screenshots/ListTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/ListTheme_light.png b/scripts/javascript/screenshots/ListTheme_light.png
new file mode 100644
index 0000000000..39c0ddbcf3
Binary files /dev/null and b/scripts/javascript/screenshots/ListTheme_light.png differ
diff --git a/scripts/javascript/screenshots/MultiButtonTheme_dark.png b/scripts/javascript/screenshots/MultiButtonTheme_dark.png
new file mode 100644
index 0000000000..0d93d13c9f
Binary files /dev/null and b/scripts/javascript/screenshots/MultiButtonTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/MultiButtonTheme_light.png b/scripts/javascript/screenshots/MultiButtonTheme_light.png
new file mode 100644
index 0000000000..6d6b8ff350
Binary files /dev/null and b/scripts/javascript/screenshots/MultiButtonTheme_light.png differ
diff --git a/scripts/javascript/screenshots/PaletteOverrideTheme_dark.png b/scripts/javascript/screenshots/PaletteOverrideTheme_dark.png
new file mode 100644
index 0000000000..6218b8f157
Binary files /dev/null and b/scripts/javascript/screenshots/PaletteOverrideTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/PaletteOverrideTheme_light.png b/scripts/javascript/screenshots/PaletteOverrideTheme_light.png
new file mode 100644
index 0000000000..56882b0872
Binary files /dev/null and b/scripts/javascript/screenshots/PaletteOverrideTheme_light.png differ
diff --git a/scripts/javascript/screenshots/PickerTheme_dark.png b/scripts/javascript/screenshots/PickerTheme_dark.png
new file mode 100644
index 0000000000..7f93bcf793
Binary files /dev/null and b/scripts/javascript/screenshots/PickerTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/PickerTheme_light.png b/scripts/javascript/screenshots/PickerTheme_light.png
new file mode 100644
index 0000000000..68fb97146f
Binary files /dev/null and b/scripts/javascript/screenshots/PickerTheme_light.png differ
diff --git a/scripts/javascript/screenshots/ShowcaseTheme_dark.png b/scripts/javascript/screenshots/ShowcaseTheme_dark.png
new file mode 100644
index 0000000000..181ffbf0d0
Binary files /dev/null and b/scripts/javascript/screenshots/ShowcaseTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/ShowcaseTheme_light.png b/scripts/javascript/screenshots/ShowcaseTheme_light.png
new file mode 100644
index 0000000000..0ff95d7827
Binary files /dev/null and b/scripts/javascript/screenshots/ShowcaseTheme_light.png differ
diff --git a/scripts/javascript/screenshots/SpanLabelTheme_dark.png b/scripts/javascript/screenshots/SpanLabelTheme_dark.png
new file mode 100644
index 0000000000..aff6e3e22a
Binary files /dev/null and b/scripts/javascript/screenshots/SpanLabelTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/SpanLabelTheme_light.png b/scripts/javascript/screenshots/SpanLabelTheme_light.png
new file mode 100644
index 0000000000..4107b253db
Binary files /dev/null and b/scripts/javascript/screenshots/SpanLabelTheme_light.png differ
diff --git a/scripts/javascript/screenshots/SwitchTheme_dark.png b/scripts/javascript/screenshots/SwitchTheme_dark.png
new file mode 100644
index 0000000000..9db37343b8
Binary files /dev/null and b/scripts/javascript/screenshots/SwitchTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/SwitchTheme_light.png b/scripts/javascript/screenshots/SwitchTheme_light.png
new file mode 100644
index 0000000000..cde93e2d1c
Binary files /dev/null and b/scripts/javascript/screenshots/SwitchTheme_light.png differ
diff --git a/scripts/javascript/screenshots/TabsTheme_dark.png b/scripts/javascript/screenshots/TabsTheme_dark.png
new file mode 100644
index 0000000000..93b7fb18b6
Binary files /dev/null and b/scripts/javascript/screenshots/TabsTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/TabsTheme_light.png b/scripts/javascript/screenshots/TabsTheme_light.png
new file mode 100644
index 0000000000..620d731887
Binary files /dev/null and b/scripts/javascript/screenshots/TabsTheme_light.png differ
diff --git a/scripts/javascript/screenshots/TextFieldTheme_dark.png b/scripts/javascript/screenshots/TextFieldTheme_dark.png
new file mode 100644
index 0000000000..8f63689ab3
Binary files /dev/null and b/scripts/javascript/screenshots/TextFieldTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/TextFieldTheme_light.png b/scripts/javascript/screenshots/TextFieldTheme_light.png
new file mode 100644
index 0000000000..09b3be4eeb
Binary files /dev/null and b/scripts/javascript/screenshots/TextFieldTheme_light.png differ
diff --git a/scripts/javascript/screenshots/ToolbarTheme_dark.png b/scripts/javascript/screenshots/ToolbarTheme_dark.png
new file mode 100644
index 0000000000..5c7ccc02de
Binary files /dev/null and b/scripts/javascript/screenshots/ToolbarTheme_dark.png differ
diff --git a/scripts/javascript/screenshots/ToolbarTheme_light.png b/scripts/javascript/screenshots/ToolbarTheme_light.png
new file mode 100644
index 0000000000..c42d5a64b5
Binary files /dev/null and b/scripts/javascript/screenshots/ToolbarTheme_light.png differ