Add JUnit 5 test support for the JavaSE simulator#5032
Merged
Conversation
App developers can now write standard @test methods against the Codename One simulator instead of (or alongside) the legacy AbstractTest / DeviceRunner framework. The new com.codename1.testing.junit package lives in the JavaSE port -- simulator-only, so tests get full JVM reflection and can use Mockito/AssertJ/etc. that ParparVM cannot run on device. Annotations: - @CodenameOneTest -- meta @ExtendWith(CodenameOneExtension.class) - @RunOnEdt -- dispatch the test body (and lifecycle when class-level) through CN.callSerially with a latch so throwables surface on the JUnit thread - @SimulatorProperty (+ @SimulatorProperties container, since the port is source 1.7 and predates @repeatable) -- SYSTEM scope before Display init, DISPLAY scope after - @theme, @DarkMode, @LargerText, @orientation, @rtl -- per-test visual config applied on the EDT in one batch followed by a single theme refresh; method-level overrides class-level JavaSEPort gains two public, non-persisting setters (setSimulatorPortrait, setSimulatorLargerTextScale) so the extension can drive accessibility / orientation without reflection, and an isPortrait() override that honors an explicit-portrait flag (the default canvas-derived inference reads as landscape on any wide host window, which broke the orientation case in tests). JUnit Jupiter moves from test to provided scope in the javase pom so the support classes compile but the JUnit dependency does not leak onto the simulator's runtime classpath for apps that do not opt in. 15 new tests in CodenameOneExtensionTest cover every annotation end-to-end; full javase suite stays green at 63 tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
setup-workspace.sh runs two mvn invocations: the first passes -Dcodename1.platform=javase (which activates local-dev-javase and pulls jcef.jar + jfxrt.jar onto the simulator's compile classpath), the second doesn't. As long as nothing forced javase to recompile in that second pass, master got away with it -- target/classes from the first pass already had CEF / JavaFX .class files. The JUnit-support work in this branch adds new source files under Ports/JavaSE/src/com/codename1/ testing/junit/ which is enough to flip maven-compiler-plugin's incremental detection into a full rebuild, and the rebuild then fails because jcef.jar is no longer visible. Switch the activation to mirror the maven/android compile-android profile -- fire whenever cn1.binaries is a real directory and the property is set. The script passes -Dcn1.binaries to both invocations, so the profile now activates in both passes and the recompile (when it does happen) has everything it needs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collaborator
Author
|
Compared 19 screenshots: 19 matched. |
scripts/run-javase-simulator-integration-tests.sh (and the top-level ant test-javase target used by PR CI) build the JavaSE port through the NetBeans-style Ports/JavaSE/build.xml. That Ant build has its own javac.classpath -- it does not see Maven scopes -- and JUnit Jupiter is not on it. The new com/codename1/testing/junit support classes are meant to ship via the codenameone-javase Maven artifact, where junit-jupiter is a "provided" dep. They are not needed for the simulator's screenshot integration tests, so just skip them on the Ant side. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collaborator
Author
|
Compared 110 screenshots: 110 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
Collaborator
Author
|
Compared 110 screenshots: 110 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Collaborator
Author
|
Compared 11 screenshots: 11 matched. |
Collaborator
Author
|
Compared 110 screenshots: 110 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
The PR CI's build-test matrix runs Surefire inside a Linux container with no X11 display. AWT's GraphicsEnvironment auto-detects headless mode but never sets the java.awt.headless system property, so the @DisabledIfSystemProperty guard on CodenameOneExtensionTest didn't fire. The extension then tried Display.init(null), which goes through JavaSEPort.init -> new JFrame() and threw HeadlessException inside @BeforeAll. JUnit marked the class as errored, and worse the Display singleton was left half-initialized, hanging the next test class (SimulatorHookLoaderTest) until the CI 6-hour timeout fired. Fix: have CodenameOneExtension.beforeAll check GraphicsEnvironment.isHeadless() first and throw TestAbortedException with an explanatory message. JUnit reports that as "skipped" instead of "failed", and crucially the JVM never enters Display.init so the shared singleton stays clean for any later tests. Verified locally: mvn test -DargLine="-Djava.awt.headless=true" reports "Tests run: 15, Skipped: 15" instead of erroring. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
End-user-facing changes to make the new com.codename1.testing.junit API usable out of the box: * maven/cn1app-archetype/.../common/pom.xml: add codenameone-javase and junit-jupiter at test scope so common can compile JUnit tests that live in common/src/test/java. Surefire stays skipped here to avoid running each JUnit test twice (javase/pom.xml mounts the same sources via testSourceDirectory). * maven/cn1app-archetype/.../javase/pom.xml: add junit-jupiter at test scope. Surefire runs from this module and now actually has the JUnit Jupiter engine on its classpath. The codenameone-javase artifact's "provided" scope for junit-jupiter still keeps it out of user app classpaths that do not opt in. * scripts/initializr/.../skill/references/junit-testing.md: new skill reference covering the JUnit 5 path end-to-end -- when to use it vs AbstractTest, dependency setup, every annotation with a worked example, EDT dispatch semantics, the headless behavior and why CodenameOneExtensionTest is gated for it, coexistence with cn1:test, side-by-side example. * scripts/initializr/.../skill/SKILL.md: index the new reference and expand the Testing section so the choice between AbstractTest and @CodenameOneTest is the first thing a contributor sees. * scripts/initializr/.../skill/references/testing-and-screenshots.md: add a "Two ways to write tests" preamble that points at the new junit-testing.md so users find the JUnit option from either entry. * docs/developer-guide/Testing-with-JUnit.adoc: long-form chapter for the manual covering the same material as the skill reference but in AsciiDoc, with a comparison table, dependency snippets, annotation reference, headless explanation, and side-by-side worked example. Wired into developer-guide.asciidoc next to the performance chapter so it sits near the existing screenshot testing material. Verified by mvn install of the archetype, archetype:generate of a new app from it, and javac-compiling a sample @CodenameOneTest against the locally-installed codenameone-javase 8.0-SNAPSHOT + junit-jupiter-api 5.9.3 artifacts. JavaSE port suite remains at 63 passing tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
Contributor
Cloudflare Preview
|
The dev-guide CI runs Vale with the Microsoft style and fails the build on any error-level alert. The new chapter tripped four errors (Contractions, Foreign 'e.g.', two hyphenated 'auto-...') and five warnings (HeadingPunctuation, Adverbs, We). Reworded: * "vs." -> "versus" in the section heading. * Dropped the "freely" / "partially" / "auto-" adverbs and rewrote the sentences without them. * "e.g." -> "for example". * "our own internal" -> "the framework's internal" to avoid the first-person plural. Vale now reports 0 errors, 0 warnings, 0 suggestions for this file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The dev-guide aggregate quality gate fails when LANGUAGETOOL_COUNT != 0 (not just when LANGUAGETOOL_STATUS != 0). The Testing-with-JUnit chapter introduced four spelling-rule matches that LanguageTool's English dictionary doesn't carry: * rethrown -- past tense of "rethrow" (already accepted) and "rethrows" (already accepted). Adding the third form for completeness. * Throwable / Throwables -- java.lang.Throwable and its plural, used when describing the EDT dispatch semantics. * javase -- the lowercase Maven module name. * Xvfb -- X virtual framebuffer, named in the headless-runner section. These are all legitimate technical terms used throughout the chapter, so they belong in the accept list rather than reworded. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The @theme annotation now accepts a native-theme alias in addition to a resource path. The new NativeTheme enum mirrors the simulator's Simulate > Native Theme menu (iOS Modern / iOS Flat / iPhone Pre-Flat / Android Material / Android Holo Light / Android Legacy) and carries both the .res resource path and the human-readable label that menu displays. The extension prefers the enum form when both are set; a non-empty value() is used only when nativeTheme is left at NONE. @test @theme(nativeTheme = NativeTheme.IOS_MODERN) ... @test @theme(nativeTheme = NativeTheme.ANDROID_MATERIAL) ... @test @theme("/MyAppTheme.res") ... Docs and skill files updated to lead with the enum form, since it is the recommended path for cross-platform look-and-feel coverage; the resource-path form remains for app themes shipped under src/main/resources. Same commit removes the "legacy" framing from the AbstractTest / cn1:test path across all docs (dev-guide chapter, junit-testing.md skill, testing-and-screenshots.md skill, SKILL.md), and drops the version-anchored language ("Starting with Codename One 8", "releases >= 8.0") that I had guessed wrong. AbstractTest is the only framework that runs on a device via ParparVM and isn't going anywhere -- the two frameworks are peers and the docs now describe them by their tradeoffs (device vs. simulator-only, device subset vs. full JVM) rather than by recency. 64 javase tests pass (was 63 + a new NativeTheme test that asserts the enum's resourcePath() and displayName() carry the right values). Vale prose-lint clean on the updated chapter. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 29, 2026
shai-almog
added a commit
that referenced
this pull request
May 30, 2026
* Blog: developer workflow (on-device debugging + JUnit 5) Consolidated follow-up to the May 29 weekly index. Combines what were previously two separate posts into one themed post about how the dev iteration loop changes this release: - On-device debugging (PRs #4999 iOS + #5012 Android): JDWP through to real devices and the iOS / Android simulators, so jdb, IntelliJ, VS Code, Eclipse, and NetBeans attach against Java source. The three iOS pieces (translator instrumentation, device runtime, JDWP proxy) and the smaller adb-orchestration Android half. - JUnit 5 against the JavaSE simulator (PR #5032): @CodenameOneTest, @RunOnEdt, @SimulatorProperty, and the visual-config annotations (@theme, @DarkMode, @LargerText, @orientation, @rtl). junit-jupiter scope change so JUnit does not leak onto the simulator runtime for apps that do not opt in. Both together push the dev loop on device closer to what it looks like on a regular JVM application. * Developer-workflow post: style pass - "I" voice replaced with "we" or "Codename One"-flavoured phrasing throughout. Opening hook, "this is the Java code I wrote" line, the "thing that surprised me" section, the wrap-up. - Heading and prose for "the thing that surprised me" reworked so the surprise is framed as something the team noticed rather than a personal anecdote. - Wrap-up rephrased so the "two pieces of paving" sentence reads as a team observation rather than a personal opinion. * Developer-workflow post: full rewrite per review - New opening that does not start with the "Two things this post" fragment. - Supported targets clarified: iOS = simulator or real iPhone over Wi-Fi from a Mac (the "USB pairing" language is gone). Android = USB, wireless adb, or the emulator (all three). - New paragraph spelling out that iOS still needs a Mac in the pipeline because the bits come from Xcode (locally or via the build server). - Dev-guide references go to the HTML version on the website (/developer-guide/#_on_device_debugging_ios etc.), not to the asciidoc source. - Step-by-step IntelliJ tutorial for iOS: enable the four build hints (using the in-settings names, no codename1.arg. prefix), recommend leaving the block-on-load option on so the on-device-debug variant is visible, run the proxy, attach the debugger, launch the app. - Same shape Android tutorial. - Device-console behaviour and its caveat called out explicitly. - Capability note up front: Android can step through native C/C++ via Android Studio's LLDB alongside the JDWP attach; iOS cannot. - "The thing that surprised us" section removed. - "Why the two changes pair" section removed. - JUnit section rewritten: explicit that it is standard JUnit 5 (not a fork), com.codename1.testing.junit holds annotations + CodenameOneExtension, AbstractTest stays for on-device tests. - Tutorial: file location, minimal example, plain-validator variant, visual-config variant, @SimulatorProperty variant. - Testcontainers / WireMock no longer in the example list. - Hero image lands at /blog/developer-workflow-debug-and-junit.jpg. - IntelliJ debugger screenshot lands at /blog/developer-workflow-debug-and-junit/intellij-debugger-on-device.png. - "Back to the weekly index" link to the intro post in the wrap-up. * Workflow post: fix Mac framing, add Mermaid for flow diagrams Two reviewer-driven fixes. 1. The "iOS still needs a Mac somewhere in the pipeline" framing was technically true but misleading -- the build cloud has a Mac, so a developer working on Linux or Windows does not need a local one to debug a real iPhone. The supported-targets section now says that explicitly: the Mac is only required for the local Xcode build path and for the iOS Simulator. The Build Cloud handles the iOS build and the JDWP attach works over Wi-Fi from any OS. 2. The Android paragraph no longer talks about not-needing-a-Mac (redundant). It now talks about needing the Android SDK platform tools (adb) on the developer machine, and that those are available on macOS, Linux, and Windows. 3. Replaced both ASCII data-flow boxes with real Mermaid diagrams. New {{< mermaid >}} shortcode + an inline ESM loader (gated by window.__cn1MermaidLoaded so multiple diagrams on one page only initialise once). The loader can't live in extend_footer.html because PaperMod calls that partial via partialCached keyed on layout/kind, so any per-page conditional content there ends up shared across every page sharing the same cache key. Inlining the loader in the shortcode dodges that completely; pages without a mermaid shortcode never see the loader. Verified locally with `hugo --buildFuture`: the developer-workflow post is the only page on the site that emits the loader. * website: render future-dated posts on PR previews The Cloudflare PR preview build was hiding the new on-device-debug post because its front-matter date (2026-05-30) is one day past the current build date. Hugo skips future-dated posts unless --buildFuture is set. - scripts/website/build.sh now reads HUGO_BUILD_FUTURE (default "false") and HUGO_BUILD_DRAFTS (default "false") and passes --buildFuture / --buildDrafts to Hugo when set. Production behaviour unchanged. - .github/workflows/website-docs.yml sets HUGO_BUILD_FUTURE=true on pull_request runs and "false" on the merged-to-master deploy. So reviewers see staged posts on the *.pages.dev preview; the live site still only shows posts on or after their publish date. * Workflow post: review fixes - JUnit example now boots the project's app class through init() / start() instead of constructing a Form in the test. Asserts against the form the app actually opens; that exercises the same startup path the simulator runs, which is what most apps want to test. - Maven command is "mvn -Ptest test" (the project's test profile), not "mvn -pl javase test". - Added IntelliJ-gutter screenshot and JUnit-results screenshot to the test tutorial. The screenshots make the IDE flow the primary example; the command-line version is below. - The "fastest iteration loop" remark on the local-Xcode-build bullet is removed; both build paths are presented as equivalent. - Block-on-load reasoning rewritten. The concern is not "an app that does nothing"; it is (a) launching an on-device-debug build while the proxy is not running and not realising it is silently waiting, and (b) mistaking the on-device-debug build for a release build and being surprised when it does not perform as smoothly. - Native-interface capability section rewritten. The previous "Android Studio's LLDB lets you step into C / C++" framing was wrong; Codename One native interfaces are Java on Android, so the JDWP attach steps through them like any other class. On iOS the Impl is Objective-C; JDWP can't step through it, but framework code, your own Java, and the values the call returns are all still available, so most workflows still hold. Attach Xcode in parallel for the Objective-C body if you need it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
@Testmethods against the Codename One simulator instead of (or alongside) the legacyAbstractTest/DeviceRunnerframework. Newcom.codename1.testing.junitpackage lives in the JavaSE port -- simulator-only, so tests get full JVM reflection and can use Mockito/AssertJ/etc. that ParparVM cannot run on device.@CodenameOneTest(meta@ExtendWith),@RunOnEdt,@SimulatorProperty(+ container@SimulatorProperties), and a visual-config set --@Theme,@DarkMode,@LargerText,@Orientation,@RTL-- resolved method-level over class-level, applied on the EDT in one batch followed by a single theme refresh.JavaSEPortadditions to support the visual annotations cleanly without reflection:setSimulatorPortrait,setSimulatorLargerTextScale, and anisPortrait()override that honors an explicit-portrait flag (the canvas-derived inference reads as landscape on any wide host window, breaking the orientation case in tests).junit-jupitermoves fromtesttoprovidedscope in the javase pom so the support classes compile but the JUnit dependency does not leak onto the simulator's runtime classpath for apps that do not opt in.Example
Test plan
mvn -pl javase test -Plocal-dev-javasepasses (63 tests total: 48 pre-existing + 15 new inCodenameOneExtensionTest)@SimulatorPropertyat class/method/container,@RunOnEdton/off,@Theme(against bundlediOSModernTheme.res),@LargerTextat 1.0/1.6,@Orientationboth directions,@DarkModeenabled/disabled,@RTLenabled/disabledjunit-jupiterin test scope)@DisabledIfSystemProperty(named="java.awt.headless", matches="true")becauseJavaSEPort.init(null)constructs aJFrame; a follow-up could thread a headless-friendly init pathNotes
@SimulatorPropertyis not@Repeatableand@SimulatorPropertiesexists as the explicit container.@AfterEach; the extension never resets state the caller did not ask for.🤖 Generated with Claude Code