Add JUnit 5 test support for the JavaSE simulator#5032
Open
shai-almog wants to merge 8 commits into
Open
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 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