diff --git a/.gitignore b/.gitignore index 0be38590..91cf2899 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ target spotless-index .since-index +*.iml +.idea diff --git a/CLAUDE.md b/CLAUDE.md index 67522f20..2e208bf4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,7 +17,6 @@ each Vaadin component in a *tester* that drives it the way a browser would. - Java 21+, Maven (multi-module) - Vaadin 25 / Flow — a `provided` dependency, one fixed version per branch -- Kotlin for the older mock and internal layer in `shared/src/main/kotlin` - JUnit 6 (Jupiter) for the test API and for this repository's own tests - An annotation processor (`locator-processor`) that generates the typed locator API at build time @@ -84,7 +83,7 @@ mvn test -pl junit6 -Dtest=BasicGridTesterTest#basicGrid_selectionOnClick # Run the tests matching a pattern mvn test -pl junit6 -Dtest="*ComboBox*Test" -# Generate the Javadoc/Dokka artifacts the way CI does +# Generate the Javadoc artifacts the way CI does mvn clean install -DskipTests -Djavadocs ``` diff --git a/CONVENTIONS.md b/CONVENTIONS.md index 58769be8..5092e3cb 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -112,9 +112,7 @@ Go through the Vaadin extension points rather than around them — fire service and session lifecycle events through the `VaadinService` event bus, and forward `Instantiator` calls to the real instantiator instead of reimplementing them. -New code in `shared` is written in Java. The Kotlin sources under -`shared/src/main/kotlin` are the older mock and internal layer and are being -ported to Java; do not add new Kotlin files there. +This repository is written in Java; do not add Kotlin sources. See [`guidelines/architecture.md`](guidelines/architecture.md). @@ -152,6 +150,13 @@ detail. Javadoc describes the code today, not what changed. Change history belongs in commit messages. +In a tester, spell cross-package `@link` and `@throws` targets out in full: +`locator-processor` copies the Javadoc into the generated `*Locator`, which has +no imports. Every module builds clean under doclint, warnings included, so every +published member needs a description, its `@param`s and its `@return` — a class +that would otherwise get an undocumented default constructor declares an +explicit one. + See [`guidelines/documenting.md`](guidelines/documenting.md). ## Testing diff --git a/de-kotlin-plan.md b/de-kotlin-plan.md new file mode 100644 index 00000000..f69e7fa2 --- /dev/null +++ b/de-kotlin-plan.md @@ -0,0 +1,499 @@ +# Migrating the Kotlin sources to plain Java + +Working plan for removing Kotlin from `browserless-test`. The goal is that +`shared/src/main` is 100% Java and that downstream consumers of +`browserless-test-shared` pull no Kotlin runtime transitively. + +## Current state + +| Area | Files | LOC | Notes | +| --- | --- | --- | --- | +| `shared/src/main/kotlin/…/mocks` | 12 | 1,721 | servlet API mocks, `MockService`, `MockedUI` | +| `shared/src/main/kotlin/…/internal` | 12 | 3,043 | `MockVaadin`, `Locator`, `Routes`, `PrettyPrintTree`, utilities | +| `shared/src/main/kotlin/…/component/Grid.kt` | 1 | 901 | the heaviest single file | +| `junit6/src/test/kotlin` | 23 | ~3,050 | DynaTest + Karibu DSL | +| `shared/src/test/kotlin` | 2 | 144 | DynaTest | + +All of it is ported as of Phase 6; the table is the starting point the phases +below work through. + +`spring`, `quarkus`, `junit6/src/main` and `locator-processor` are already pure +Java, but about 48 Java files reference the Kotlin packages and 24 call sites go +through Kotlin file facades (`LocatorKt`, `PrettyPrintTreeKt`, `GridKt`, +`UtilsKt`, `BasicUtilsKt`, `ShortcutsKt`). + +## Prior art + +`origin/feat/no-kotlin-grid` carries a complete five-commit port of all 25 +main-source Kotlin files, verified green on all four suites +(20 / 1007 / 29 / 27 tests). It branched from `a333ccc` and is 82 commits behind +`main`, so it is a source to rebase from, not a branch to merge. + +Reviewer notes from that effort live on the branch in `de-kotlin-review/`. + +Drift to reconcile while rebasing — most files differ only by the +commercial → Apache license header swap (`08abcfa`); the real drift is: + +| File | Drift | Why | +| --- | --- | --- | +| `internal/MockVaadin.kt` | +222 | reload / window-name plumbing, `liveUI`, `fireSessionDestroyAndDrain`, multi-user session objects | +| `mocks/MockInstantiator.kt` | +118 | deprecated, forwarders hand-unrolled | +| `internal/PrettyPrintTree.kt` | +93 | `hrefValue()` already rewritten in plain Java reflection — expect a conflict with Phase 3 | +| `component/Grid.kt` | +86 | selection and click additions | +| `mocks/MockedUI.kt` | +82 | `navigate()` override and `toLocation()` | +| `internal/TestingLifecycleHook.kt` | +60 | slot / children rules | +| `mocks/MockRequest.kt` | +60 | role checker, principal provider | +| `mocks/MockHttpSession.kt` | +39 | `changeSessionId()` | + +`df099d0` already dropped `kotlin-reflect` on `main`, so that dependency win is +banked independently of this work. + +## Phases + +Each phase is independently mergeable and leaves the build green. + +### Phase 0 — prerequisites + +Decisions that constrain later phases: + +- **Nullability annotations.** 11 Java files in `shared`, `spring` and + `quarkus` import `org.jetbrains.annotations.@NotNull` / `@Nullable`, which + arrives transitively via `kotlin-stdlib`. Move them to JSpecify (already used + in 5 files) or add a direct `org.jetbrains:annotations` dependency. Without + this the build breaks the moment `kotlin-stdlib` leaves compile scope. +- **`SearchSpec.count`.** Pick the replacement for `kotlin.ranges.IntRange` + (the prior effort introduced a small `CountRange` helper). This constrains + Phases 4 and 5. +- **Test-side Kotlin.** In or out of scope — see below. + +### Phase 1 — `mocks/` — done + +12 Kotlin files → 14 Java files, on `refactor/no-kotlin-mocks`. All five suites +match the `main` baseline exactly: shared 42, junit6 1406, junit6-cdi-tests 4, +spring 41, quarkus 31. + +- `MockHttpEnvironment.kt` splits into `MockHttpEnvironment`, + `MockServletConfig` and a `MockUtils.putOrRemove` helper. +- `MockVaadinServlet.kt`'s top-level factories (`serviceSafe`, + `createVaadinServletRequest` / `…Response`, `_createVaadinSession`, + `WebBrowser(request)`) become statics on `MockVaadinServlet`. The last one is + renamed `createWebBrowser` to avoid clashing with + `com.vaadin.flow.server.WebBrowser`. +- `SessionAttributeMap` becomes a public class; the `HttpSession.attributes` + extension property disappears and callers construct it directly. +- `MockInstantiator`'s `Instantiator by delegate` is unrolled by hand — see the + silent-risk list below. +- `MockRequest` keeps an explicit `setUserInRole(BiPredicate)` setter, matching + what Kotlin's `is`-prefix property convention emitted. +- `MockVaadinServlet.createServletService` now declares + `throws ServiceException`, as its `VaadinServlet` superclass does — see the + breaking changes below. +- KDoc is rewritten as Javadoc rather than carried over verbatim: `[Foo]` + becomes `{@link Foo}`, `*` bullets become ` * The class is intentionally opened, to be extensible in user's library. * + *

+ * For internal use only. May be renamed or removed in a future release. + * * @since 1.0 */ public class MockQuarkusServletService extends QuarkusVaadinServletService { diff --git a/shared/pom.xml b/shared/pom.xml index cb432d2b..f77ec17a 100644 --- a/shared/pom.xml +++ b/shared/pom.xml @@ -15,103 +15,31 @@ - - - org.jetbrains.kotlin - kotlin-maven-plugin - - - compile - - compile - - - - ${project.basedir}/src/main/kotlin - ${project.basedir}/src/main/java - - - - - test-compile - - test-compile - - - - ${project.basedir}/src/test/kotlin - ${project.basedir}/src/test/java - - - - - org.apache.maven.plugins maven-compiler-plugin - - - - default-compile - none - - - - default-testCompile - none - - - java-compile - compile - - compile - - - - - com.vaadin - browserless-test-locator-processor - ${project.version} - - - - - - java-test-compile - test-compile - - testCompile - - - + + + + com.vaadin + browserless-test-locator-processor + ${project.version} + + + - + - org.jetbrains.dokka - dokka-maven-plugin + org.apache.maven.plugins + maven-javadoc-plugin org.codehaus.mojo build-helper-maven-plugin - - add-kotlin-sources-for-source-jar - package - - add-source - - - - src/main/kotlin - - - add-generated-locator-sources generate-sources @@ -170,11 +98,14 @@ test + - org.jetbrains.kotlin - kotlin-stdlib - ${kotlin.version} + org.junit.jupiter + junit-jupiter + test + io.github.classgraph classgraph @@ -196,13 +127,6 @@ true - - com.github.mvysny.dynatest - dynatest - 0.25 - test - - jakarta.servlet jakarta.servlet-api diff --git a/shared/src/main/java/com/vaadin/browserless/BaseBrowserlessTest.java b/shared/src/main/java/com/vaadin/browserless/BaseBrowserlessTest.java index e5bb70c0..9a8a3df3 100644 --- a/shared/src/main/java/com/vaadin/browserless/BaseBrowserlessTest.java +++ b/shared/src/main/java/com/vaadin/browserless/BaseBrowserlessTest.java @@ -53,10 +53,21 @@ */ public abstract class BaseBrowserlessTest { + /** + * Creates the test base; subclasses are instantiated by the test engine. + */ + protected BaseBrowserlessTest() { + } + private TestSignalEnvironment signalsTestEnvironment; private BrowserlessConfiguration resolvedConfiguration; private boolean classScopedConfiguration; + /** + * Equivalent to {@code discoverRoutes(scanPackages())}. + * + * @return the routes found in the scanned packages + */ protected synchronized Routes discoverRoutes() { return discoverRoutes(scanPackages()); } @@ -64,6 +75,8 @@ protected synchronized Routes discoverRoutes() { /** * Discover and return Routes for mocked Vaadin core system. * + * @param packageNames + * the packages to scan for routes * @see #initVaadinEnvironment() * @return Routes */ @@ -129,6 +142,10 @@ protected final Set> allLookupServices( return services; } + /** + * Registers the signals test environment, so that signal-backed components + * work inside the mocked environment. + */ protected void initSignalsSupport() { signalsTestEnvironment = TestSignalEnvironment.register(); } @@ -146,6 +163,12 @@ protected void scanTesters() { } } + /** + * Collects the packages to scan for routes, from the {@code @ViewPackages} + * annotation on the test class. + * + * @return the packages to scan, empty to scan the whole classpath + */ protected Set scanPackages() { Set packagesToScan = new HashSet<>(); @@ -284,10 +307,10 @@ void setResolvedConfiguration(BrowserlessConfiguration configuration, /** * Navigate to the given view class if it is registered. * - * @param navigationTarget - * view class to navigate to * @param * view type + * @param navigationTarget + * view class to navigate to * @return instantiated view */ public T navigate(Class navigationTarget) { @@ -297,14 +320,14 @@ public T navigate(Class navigationTarget) { /** * Navigate to view with url parameter. * + * @param + * parameter type + * @param + * view type * @param navigationTarget * view class to navigate to * @param parameter * parameter to send to view - * @param - * view type - * @param - * parameter type * @return instantiated view */ public > T navigate( @@ -322,13 +345,13 @@ public > T navigate( * with a query string, write it into the location given to * {@link #navigate(String, Class)}. * + * @param + * view type * @param navigationTarget * view class to navigate to * @param parameters * route parameters of the target's route template, keyed by * parameter name - * @param - * view type * @return instantiated view */ public T navigate(Class navigationTarget, @@ -346,13 +369,13 @@ public T navigate(Class navigationTarget, * {@code "order/ORD-1?tab=history"}, whose query parameters the view reads * from the navigation event. * + * @param + * view type * @param location * location string for navigating, optionally with a query string * and a fragment * @param expectedTarget * class that is expected for navigation - * @param - * view type * @return instantiated view */ public T navigate(String location, @@ -380,10 +403,10 @@ public HasElement reload() { * Simulates a page reload (see {@link #reload()}) and verifies the * resulting view is of the expected type. * - * @param expectedTarget - * the expected view class after reload * @param * the view type + * @param expectedTarget + * the expected view class after reload * @return the view shown after the reload * @since 25.4 */ @@ -414,11 +437,36 @@ public HasElement getCurrentView() { } // Protected for access by adapter subclass in legacy module + /** + * Equivalent to {@code TesterRegistry.wrap(component)}. + * + * @param + * the item type + * @param component + * the component to wrap + * @return a tester for the given component + * @param + * the component type + */ protected static , Y extends Component> T internalWrap( Y component) { return TesterRegistry.wrap(component); } + /** + * Wraps the component in the given tester type, preferring a more specific + * tester registered through {@code @Tests} when one exists. + * + * @param + * the tester type + * @param + * the component type + * @param wrap + * the tester type the caller asked for + * @param component + * the component to wrap + * @return a tester for the given component + */ @SuppressWarnings({ "unchecked", "rawtypes" }) protected static , Y extends Component> T internalWrap( Class wrap, Y component) { @@ -488,10 +536,10 @@ public , Y extends Component> T test( * context menu, and {@code GridTester.contextMenu(row).open()} for a grid * context menu. * - * @param componentType - * the type of the component(s) to search for * @param * the type of the component(s) to search for + * @param componentType + * the type of the component(s) to search for * @return a query object for finding components * @since 1.1 */ @@ -508,12 +556,12 @@ public ComponentQuery find( * Searches the same server-side component tree as {@link #find(Class)}, see * there for what that tree does not contain. * + * @param + * the type of the component(s) to search for * @param componentType * the type of the component(s) to search for * @param fromThis * component used as starting element for search. - * @param - * the type of the component(s) to search for * @return a query object for finding components * @since 1.1 */ @@ -529,10 +577,10 @@ public ComponentQuery find(Class componentType, * Searches the same server-side component tree as {@link #find(Class)}, see * there for what that tree does not contain. * - * @param componentType - * the type of the component(s) to search for * @param * the type of the component(s) to search for + * @param componentType + * the type of the component(s) to search for * @return a query object for finding components * @since 1.1 */ @@ -544,10 +592,10 @@ public ComponentQuery findInView( /** * Gets a query object for finding a component inside the UI. * - * @param componentType - * the type of the component(s) to search for * @param * the type of the component(s) to search for + * @param componentType + * the type of the component(s) to search for * @return a query object for finding components * @deprecated since 1.1, for removal in 26.0; use {@link #find(Class)} * instead. @@ -561,12 +609,12 @@ public ComponentQuery findInView( * Gets a query object for finding a component nested inside the given * component. * + * @param + * the type of the component(s) to search for * @param componentType * the type of the component(s) to search for * @param fromThis * component used as starting element for search. - * @param - * the type of the component(s) to search for * @return a query object for finding components * @deprecated since 1.1, for removal in 26.0; use * {@link #find(Class, Component)} instead. @@ -580,10 +628,10 @@ public ComponentQuery findInView( /** * Gets a query object for finding a component inside the current view. * - * @param componentType - * the type of the component(s) to search for * @param * the type of the component(s) to search for + * @param componentType + * the type of the component(s) to search for * @return a query object for finding components * @deprecated since 1.1, for removal in 26.0; use * {@link #findInView(Class)} instead. diff --git a/shared/src/main/java/com/vaadin/browserless/BrowserlessConfiguration.java b/shared/src/main/java/com/vaadin/browserless/BrowserlessConfiguration.java index 260bd3db..d8d03023 100644 --- a/shared/src/main/java/com/vaadin/browserless/BrowserlessConfiguration.java +++ b/shared/src/main/java/com/vaadin/browserless/BrowserlessConfiguration.java @@ -55,8 +55,17 @@ public final class BrowserlessConfiguration implements Serializable { private static final BrowserlessConfiguration EMPTY = new BrowserlessConfiguration( Map.of(), Map.of(), Set.of()); + /** + * The application properties fed to the deployment configuration. + */ private final Map applicationProperties; + /** + * The feature flags to turn on or off, by feature id. + */ private final Map featureFlags; + /** + * The extra service classes handed to the lookup initializer. + */ private final Set> lookupServices; private BrowserlessConfiguration(Map applicationProperties, diff --git a/shared/src/main/java/com/vaadin/browserless/BrowserlessDSL.java b/shared/src/main/java/com/vaadin/browserless/BrowserlessDSL.java index b8167a38..4c57427e 100644 --- a/shared/src/main/java/com/vaadin/browserless/BrowserlessDSL.java +++ b/shared/src/main/java/com/vaadin/browserless/BrowserlessDSL.java @@ -19,7 +19,7 @@ import java.util.concurrent.TimeUnit; import com.vaadin.browserless.internal.MockInternalSeverError; -import com.vaadin.browserless.internal.ShortcutsKt; +import com.vaadin.browserless.internal.Shortcuts; import com.vaadin.flow.component.Component; import com.vaadin.flow.component.HasElement; import com.vaadin.flow.component.Key; @@ -119,11 +119,10 @@ static ComponentQuery findView(UI ui, static void fireShortcut(UI ui, Key key, KeyModifier... modifiers) { if (ui.hasModalComponent()) { - ShortcutsKt._fireShortcut( - ui.getInternals().getActiveModalComponent(), key, - modifiers); + Shortcuts._fireShortcut(ui.getInternals().getActiveModalComponent(), + key, modifiers); } else { - ShortcutsKt.fireShortcut(key, modifiers); + Shortcuts.fireShortcut(key, modifiers); } } diff --git a/shared/src/main/java/com/vaadin/browserless/BrowserlessTestSetupException.java b/shared/src/main/java/com/vaadin/browserless/BrowserlessTestSetupException.java index 5ce431ec..0304121b 100644 --- a/shared/src/main/java/com/vaadin/browserless/BrowserlessTestSetupException.java +++ b/shared/src/main/java/com/vaadin/browserless/BrowserlessTestSetupException.java @@ -22,10 +22,24 @@ * @since 1.0 */ public class BrowserlessTestSetupException extends RuntimeException { + /** + * Creates an exception with the given message. + * + * @param message + * what went wrong while setting the test environment up + */ public BrowserlessTestSetupException(String message) { super(message); } + /** + * Creates an exception with the given message and cause. + * + * @param message + * what went wrong while setting the test environment up + * @param cause + * the failure this one wraps + */ public BrowserlessTestSetupException(String message, Throwable cause) { super(message, cause); } diff --git a/shared/src/main/java/com/vaadin/browserless/BrowserlessUIContext.java b/shared/src/main/java/com/vaadin/browserless/BrowserlessUIContext.java index 6c7760a2..33499ac5 100644 --- a/shared/src/main/java/com/vaadin/browserless/BrowserlessUIContext.java +++ b/shared/src/main/java/com/vaadin/browserless/BrowserlessUIContext.java @@ -21,7 +21,6 @@ import java.util.concurrent.TimeUnit; import java.util.function.Supplier; -import com.vaadin.browserless.internal.MockPage; import com.vaadin.browserless.internal.MockVaadin; import com.vaadin.browserless.locator.Locators; import com.vaadin.flow.component.Component; @@ -550,7 +549,7 @@ public boolean runPendingSignalsTasks(long maxWaitTime, TimeUnit unit) { */ public String getExternalNavigationURL() { activate(); - if (ui.getPage() instanceof MockPage mockPage) { + if (ui.getPage() instanceof MockVaadin.MockPage mockPage) { return mockPage.getLastExternalNavigationURL(); } return null; @@ -568,7 +567,7 @@ public String getExternalNavigationURL() { */ public String getExternalNavigationURL(String windowName) { activate(); - if (ui.getPage() instanceof MockPage mockPage) { + if (ui.getPage() instanceof MockVaadin.MockPage mockPage) { return mockPage.getExternalNavigationURL(windowName); } return null; @@ -590,7 +589,7 @@ public String getExternalNavigationURL(String windowName) { */ public Map> getOpenedWindows() { activate(); - if (ui.getPage() instanceof MockPage mockPage) { + if (ui.getPage() instanceof MockVaadin.MockPage mockPage) { return mockPage.getOpenedWindows(); } return Map.of(); diff --git a/shared/src/main/java/com/vaadin/browserless/CommercialTesterWrappers.java b/shared/src/main/java/com/vaadin/browserless/CommercialTesterWrappers.java index d711e4e4..513e9b09 100644 --- a/shared/src/main/java/com/vaadin/browserless/CommercialTesterWrappers.java +++ b/shared/src/main/java/com/vaadin/browserless/CommercialTesterWrappers.java @@ -33,6 +33,8 @@ public interface CommercialTesterWrappers { /** * Create a tester for the given GridPro instance. * + * @param + * the value type * @param grid * the GridPro instance to be tested * @return a GridProTester instance wrapping the given GridPro @@ -45,6 +47,8 @@ default GridProTester, V> test(GridPro grid) { /** * Create a tester for the given GridPro instance. * + * @param + * the value type * @param grid * the GridPro instance to be tested * @param itemType diff --git a/shared/src/main/java/com/vaadin/browserless/ComponentQuery.java b/shared/src/main/java/com/vaadin/browserless/ComponentQuery.java index fb557db0..19827c16 100644 --- a/shared/src/main/java/com/vaadin/browserless/ComponentQuery.java +++ b/shared/src/main/java/com/vaadin/browserless/ComponentQuery.java @@ -25,10 +25,8 @@ import java.util.function.Predicate; import java.util.stream.Stream; -import kotlin.Unit; -import kotlin.ranges.IntRange; - -import com.vaadin.browserless.internal.LocatorKt; +import com.vaadin.browserless.internal.CountRange; +import com.vaadin.browserless.internal.Locator; import com.vaadin.browserless.internal.SearchSpec; import com.vaadin.flow.component.Component; import com.vaadin.flow.component.shared.ThemeVariant; @@ -77,6 +75,8 @@ public ComponentQuery(Class componentType) { /** * Requires the given property to have expected value. * + * @param + * the value type * @param getter * the function to get the value of the property of the field, * not null @@ -101,6 +101,8 @@ public ComponentQuery withPropertyValue(Function getter, * Providing a {@literal null} value as {@code expectedValue} has no effects * since the filter will not be applied. * + * @param + * the value type * @param expectedValue * value to be compared with the one obtained by * {@link com.vaadin.flow.component.HasValue#getValue()} @@ -122,7 +124,7 @@ public ComponentQuery withValue(V expectedValue) { public ComponentQuery withId(String id) { locatorSpec.id = id; // At most one element with given id is expected - locatorSpec.count = new IntRange(0, 1); + locatorSpec.count = new CountRange(0, 1); return this; } @@ -142,7 +144,7 @@ public ComponentQuery withId(String id) { public ComponentQuery withTestId(String testId) { locatorSpec.testId = testId; // At most one element with given test-id is expected - locatorSpec.count = new IntRange(0, 1); + locatorSpec.count = new CountRange(0, 1); return this; } @@ -464,7 +466,7 @@ public ComponentQuery withResultsSize(int count) { "count must be greater or equal than zero, but was " + count); } - locatorSpec.count = new IntRange(count, count); + locatorSpec.count = CountRange.exactly(count); return this; } @@ -495,7 +497,7 @@ public ComponentQuery withResultsSize(int min, int max) { "max must be greater or equal than min, but was min=" + min + ", max=" + max + ""); } - locatorSpec.count = new IntRange(min, max); + locatorSpec.count = new CountRange(min, max); return this; } @@ -743,10 +745,10 @@ private boolean isInSlot(Component component, String slot) { * Gets a new {@link ComponentQuery} to search for given component type on * the context of first matching component for current query. * - * @param componentType - * the type of the component(s) to search for * @param * the type of the component(s) to search for + * @param componentType + * the type of the component(s) to search for * @return a new query object, to search for nested components. * @throws java.util.NoSuchElementException * if first component is found @@ -764,10 +766,12 @@ public ComponentQuery thenOnFirst( * the actual number of components found results in an * {@link IndexOutOfBoundsException}. * - * @param componentType - * the type of the component(s) to search for * @param * the type of the component(s) to search for + * @param index + * the 1-based index of the match to pick + * @param componentType + * the type of the component(s) to search for * @return a new query object, to search for nested components. * @see #atIndex(int) * @throws IllegalArgumentException @@ -844,6 +848,8 @@ public T last() { * the actual number of components found results in an * {@link IndexOutOfBoundsException}. * + * @param index + * the 1-based index of the match to pick * @return the component of the type specified in the constructor. * @throws IllegalArgumentException * if index is zero or negative @@ -925,10 +931,9 @@ public boolean exists() { */ public List all() { if (context != null) { - return LocatorKt._find(context, componentType, - locatorSpec::populate); + return Locator._find(context, componentType, locatorSpec::populate); } - return LocatorKt._find(componentType, locatorSpec::populate); + return Locator._find(componentType, locatorSpec::populate); } /** @@ -946,18 +951,23 @@ public ComponentQuery from(Component context) { return this; } + /** + * Runs the query and returns the single match. + * + * @return the only component the query matches + */ protected T find() { // Snapshot and restore so resolution's "expect exactly one" // constraint doesn't leak into the persistent spec and // pollute later chain steps. - IntRange savedCount = locatorSpec.count; - locatorSpec.count = new IntRange(1, 1); + CountRange savedCount = locatorSpec.count; + locatorSpec.count = CountRange.ONE; try { if (context != null) { - return LocatorKt._get(context, componentType, + return Locator._get(context, componentType, locatorSpec::populate); } - return LocatorKt._get(componentType, locatorSpec::populate); + return Locator._get(componentType, locatorSpec::populate); } catch (AssertionError e) { // Happens when found component(s) are not of the expected type throw new NoSuchElementException(e.getMessage()); @@ -981,7 +991,7 @@ private static class LocatorSpec { String placeholder; String text; boolean textExactMatch = true; - IntRange count = new IntRange(0, Integer.MAX_VALUE); + CountRange count = CountRange.ANY; Object value; final Set classes = new HashSet<>(); final Set withoutClasses = new HashSet<>(); @@ -989,7 +999,7 @@ private static class LocatorSpec { String withoutThemes; List> predicates = new ArrayList<>(0); - public Unit populate(SearchSpec spec) { + public void populate(SearchSpec spec) { if (id != null) spec.setId(id); if (testId != null) @@ -1016,8 +1026,6 @@ else if (text != null) spec.setWithoutThemes(withoutThemes); spec.setCount(count); spec.getPredicates().addAll(predicates); - - return Unit.INSTANCE; } } diff --git a/shared/src/main/java/com/vaadin/browserless/ComponentTester.java b/shared/src/main/java/com/vaadin/browserless/ComponentTester.java index 98048058..3d8872c9 100644 --- a/shared/src/main/java/com/vaadin/browserless/ComponentTester.java +++ b/shared/src/main/java/com/vaadin/browserless/ComponentTester.java @@ -29,7 +29,7 @@ import org.slf4j.LoggerFactory; import tools.jackson.databind.node.ObjectNode; -import com.vaadin.browserless.internal.PrettyPrintTreeKt; +import com.vaadin.browserless.internal.PrettyPrintTree; import com.vaadin.flow.component.AbstractCompositeField; import com.vaadin.flow.component.AbstractField; import com.vaadin.flow.component.Component; @@ -121,6 +121,8 @@ protected boolean isComponentReadOnly() { * {@link #notUsableReasons(Consumer)} to provide additional details to the * potential exception thrown by {@link #ensureComponentIsUsable()}. * + * @param component + * the component to inspect * @return {@code true} if component can be interacted with by the user * @see #notUsableReasons(Consumer) * @see #ensureComponentIsUsable() @@ -166,10 +168,10 @@ public void setModal(boolean modal) { * context menu, and {@code GridTester.contextMenu(row).open()} for a grid * context menu. * - * @param componentType - * type of the component to search. * @param * type of the component to search. + * @param componentType + * type of the component to search. * @return a {@link ComponentQuery} instance, searching for wrapped * component children. */ @@ -232,7 +234,7 @@ private static void throwIfNotUsable(Component component, Consumer> reasonsProvider) { if (!usableTest.test(component)) { StringBuilder message = new StringBuilder( - PrettyPrintTreeKt.toPrettyString(component) + PrettyPrintTree.toPrettyString(component) + " is not usable"); Stream.Builder reasons = Stream.builder(); reasonsProvider.accept(reasons::add); @@ -249,6 +251,8 @@ private static void throwIfNotUsable(Component component, * method to provide additional details to the potential exception throw by * {@link #ensureComponentIsUsable()}. * + * @param collector + * receives the components the tester walks over * @see #isUsable() * @see #ensureComponentIsUsable() */ @@ -267,6 +271,10 @@ protected void notUsableReasons(Consumer collector) { * method to provide additional details to the potential exception throw by * {@link #ensureComponentIsUsable()}. * + * @param component + * the component to check + * @param collector + * receives the components the tester walks over * @see #isUsable() * @see #ensureComponentIsUsable() */ @@ -299,11 +307,14 @@ protected void ensureVisible() { /** * Check that the given component is visible for the user. Else throw an * {@link IllegalStateException} + * + * @param component + * the component to check */ protected static void ensureVisible(Component component) { if (!component.isVisible() || !component.isAttached()) { throw new IllegalStateException( - PrettyPrintTreeKt.toPrettyString(component) + PrettyPrintTree.toPrettyString(component) + " is not visible!"); } } @@ -367,7 +378,7 @@ public void blur() { private void ensureComponentCanBeFocused() { if (!(component instanceof Focusable)) { throw new IllegalArgumentException( - PrettyPrintTreeKt.toPrettyString(component) + PrettyPrintTree.toPrettyString(component) + " is not Focusable"); } // Unlike other interactions, focus does not care about read-only: a @@ -522,12 +533,12 @@ protected void fireDomEvent(DomEvent event) { * Usually the {@link ComponentQuery} consumer should only define * conditions, not invoke any terminal operator. * + * @param + * the type of the component to search for * @param componentType * the type of the component to search for * @param queryBuilder * the function that sets query condition - * @param - * the type of the component to search for * @return the component found by query execution, wrapped into an * {@link Optional}, or empty if the query does not produce results. */ @@ -541,9 +552,8 @@ protected Optional findByQuery( StringBuilder message = new StringBuilder( "Expecting the query to produce at most one result, but got ") .append(result.size()).append(": "); - message.append( - result.stream().map(PrettyPrintTreeKt::toPrettyString) - .collect(Collectors.joining(", "))); + message.append(result.stream().map(PrettyPrintTree::toPrettyString) + .collect(Collectors.joining(", "))); throw new IllegalArgumentException(message.toString()); } return Optional.of(result.get(0)); @@ -556,12 +566,12 @@ protected Optional findByQuery( * Usually the {@link ComponentQuery} consumer should only define * conditions, not invoke any terminal operator. * + * @param + * the type of the component to search for * @param componentType * the type of the component to search for * @param queryBuilder * the function that sets query condition - * @param - * the type of the component to search for * @return the components found by query execution, or an empty list. */ protected List findAllByQuery( @@ -663,6 +673,8 @@ private void setEmptyValueAsUser() { * instance of AbstractField. This method is purposed for internal use and * when creating custom testers extending ComponentTesters. * + * @param + * the value type * @param value * the new value, may be null. */ @@ -686,6 +698,8 @@ protected void setValueAsUser(V value) { * extending ComponentTesters, for fields other than the wrapped component, * such as an editor field owned by the wrapped component. * + * @param + * the value type * @param field * the field to set the value to, not {@literal null}. * @param value diff --git a/shared/src/main/java/com/vaadin/browserless/ComponentTesterPackages.java b/shared/src/main/java/com/vaadin/browserless/ComponentTesterPackages.java index bd5599ec..d85dbb50 100644 --- a/shared/src/main/java/com/vaadin/browserless/ComponentTesterPackages.java +++ b/shared/src/main/java/com/vaadin/browserless/ComponentTesterPackages.java @@ -24,7 +24,7 @@ /** * Annotation to use to scan given packages for component wrappers outside the * default {@code com.vaadin.flow.component}. - *

+ *

* This makes adding custom component wrappers simpler as they can then use * package protected fields and methods. * @@ -37,7 +37,7 @@ /** * Array of packages to scan for {@link ComponentTester} implementations. - *

+ *

* Implementation should use the {@link Tests} annotation to be used * automatically in the {@code wraps(Component)} method. * diff --git a/shared/src/main/java/com/vaadin/browserless/ElementConditions.java b/shared/src/main/java/com/vaadin/browserless/ElementConditions.java index f6ad5683..7437f65c 100644 --- a/shared/src/main/java/com/vaadin/browserless/ElementConditions.java +++ b/shared/src/main/java/com/vaadin/browserless/ElementConditions.java @@ -58,14 +58,16 @@ private ElementConditions() { * * For example, given HTML * - *

+     * 
{@code
      * 

* Hello there now! *

- *
+ * }
* * the text that will be checked will be {@literal Hello there now!}. * + * @param + * the item type * @param text * the text the component is expected to have as its content. Not * {@literal null}. @@ -94,14 +96,16 @@ public static Predicate containsText(String text) { * * For example, given HTML * - *
+     * 
{@code
      * 

* Hello there now! *

- *
+ * }
* * the text that will be checked will be {@literal Hello there now!}. * + * @param + * the item type * @param text * the text the component is expected to have as its content. Not * {@literal null}. @@ -128,6 +132,8 @@ public static Predicate containsText(String text, * Attribute names are considered case-insensitive and all names will be * converted to lower case automatically. * + * @param + * the item type * @param attribute * the name of the attribute, not {@literal null} * @return {@literal true} if the attribute has been set, {@literal false} @@ -145,6 +151,8 @@ public static Predicate hasAttribute( * Attribute names are considered case-insensitive and all names will be * converted to lower case automatically. * + * @param + * the item type * @param attribute * the name of the attribute, not {@literal null} * @param value @@ -167,6 +175,8 @@ public static Predicate hasAttribute( * Attribute names are considered case-insensitive and all names will be * converted to lower case automatically. * + * @param + * the item type * @param attribute * the name of the attribute, not {@literal null} * @return {@literal true} if the attribute has not been set, @@ -184,6 +194,8 @@ public static Predicate hasNotAttribute( * Attribute names are considered case-insensitive and all names will be * converted to lower case automatically. * + * @param + * the item type * @param attribute * the name of the attribute, not {@literal null} * @param value @@ -216,9 +228,13 @@ public static Predicate hasNotAttribute( * {@link com.vaadin.flow.component.html.NativeLabel} (or any * {@code