From cbd7325a613567f3349365a37a29f32be877b92b Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Fri, 11 Oct 2024 14:10:54 +0300 Subject: [PATCH 01/14] feat: Add react mixed test module Add a mixed React Hilla Flow test module to test interoperability of the 2 features Fixes #4972 --- pom.xml | 7 + .../pom-dev-mode.xml | 319 ++++++++++++++++++ vaadin-platform-react-hybrid-test/pom.xml | 293 ++++++++++++++++ .../src/main/frontend/index.html | 23 ++ .../frontend/themes/fusion-test/styles.css | 0 .../frontend/themes/fusion-test/theme.json | 1 + .../src/main/frontend/utils/css-utils.ts | 23 ++ .../src/main/frontend/views/@index.tsx | 25 ++ .../src/main/frontend/views/flow/@index.tsx | 17 + .../main/frontend/views/flow/hello-hilla.tsx | 25 ++ .../src/main/frontend/views/hilla/@index.tsx | 17 + .../src/main/frontend/views/hilla/@layout.tsx | 56 +++ .../main/frontend/views/hilla/components.tsx | 315 +++++++++++++++++ .../main/frontend/views/hilla/hello-react.tsx | 24 ++ .../platform/react/test/Application.java | 37 ++ .../react/test/views/FlowHillaView.java | 31 ++ .../platform/react/test/views/FlowLayout.java | 77 +++++ .../react/test/views/FlowMainView.java | 12 + .../react/test/views/HelloWorldView.java | 31 ++ .../META-INF/resources/icons/icon.png | Bin 0 -> 15994 bytes .../META-INF/resources/images/logo.png | Bin 0 -> 16070 bytes .../src/main/resources/application.properties | 8 + .../src/main/resources/banner.txt | 5 + .../react/offline/ChromeDeviceTest.java | 229 +++++++++++++ .../react/offline/ChromeOfflineIT.java | 84 +++++ .../platform/react/offline/ComponentsIT.java | 75 ++++ 26 files changed, 1734 insertions(+) create mode 100644 vaadin-platform-react-hybrid-test/pom-dev-mode.xml create mode 100644 vaadin-platform-react-hybrid-test/pom.xml create mode 100644 vaadin-platform-react-hybrid-test/src/main/frontend/index.html create mode 100644 vaadin-platform-react-hybrid-test/src/main/frontend/themes/fusion-test/styles.css create mode 100644 vaadin-platform-react-hybrid-test/src/main/frontend/themes/fusion-test/theme.json create mode 100644 vaadin-platform-react-hybrid-test/src/main/frontend/utils/css-utils.ts create mode 100644 vaadin-platform-react-hybrid-test/src/main/frontend/views/@index.tsx create mode 100644 vaadin-platform-react-hybrid-test/src/main/frontend/views/flow/@index.tsx create mode 100644 vaadin-platform-react-hybrid-test/src/main/frontend/views/flow/hello-hilla.tsx create mode 100644 vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/@index.tsx create mode 100644 vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/@layout.tsx create mode 100644 vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/components.tsx create mode 100644 vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/hello-react.tsx create mode 100644 vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/Application.java create mode 100644 vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowHillaView.java create mode 100644 vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowLayout.java create mode 100644 vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowMainView.java create mode 100644 vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/HelloWorldView.java create mode 100644 vaadin-platform-react-hybrid-test/src/main/resources/META-INF/resources/icons/icon.png create mode 100644 vaadin-platform-react-hybrid-test/src/main/resources/META-INF/resources/images/logo.png create mode 100644 vaadin-platform-react-hybrid-test/src/main/resources/application.properties create mode 100644 vaadin-platform-react-hybrid-test/src/main/resources/banner.txt create mode 100644 vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ChromeDeviceTest.java create mode 100644 vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ChromeOfflineIT.java create mode 100644 vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ComponentsIT.java diff --git a/pom.xml b/pom.xml index b1200a64f..f33cabf56 100644 --- a/pom.xml +++ b/pom.xml @@ -76,6 +76,7 @@ vaadin-dev-bundle vaadin-prod-bundle/pom-unoptimized.xml vaadin-prod-bundle + vaadin-platform-react-hybrid-test false @@ -95,6 +96,12 @@ vaadin-platform-hybrid-test + + react-hybrid + + vaadin-platform-react-hybrid-test + + https://github.com/vaadin/platform diff --git a/vaadin-platform-react-hybrid-test/pom-dev-mode.xml b/vaadin-platform-react-hybrid-test/pom-dev-mode.xml new file mode 100644 index 000000000..fa31a9fde --- /dev/null +++ b/vaadin-platform-react-hybrid-test/pom-dev-mode.xml @@ -0,0 +1,319 @@ + + 4.0.0 + + com.vaadin + vaadin-platform-parent + 24.5-SNAPSHOT + + vaadin-platform-react-hybrid-test + jar + Vaadin Platform Hybrid Tests (Dev Mode) + Vaadin Platform Hybrid Tests (Dev Mode) + https://vaadin.com + + 17 + 17 + UTF-8 + UTF-8 + false + true + + + + + com.vaadin + vaadin-bom + ${project.version} + pom + import + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + Vaadin Directory + https://maven.vaadin.com/vaadin-addons + + false + + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + vaadin-prereleases + https://maven.vaadin.com/vaadin-prereleases + + + vaadin-addons + https://maven.vaadin.com/vaadin-addons + + + + + + com.vaadin + vaadin + + + com.vaadin + vaadin-spring + + + org.springframework.boot + spring-boot-starter-web + + + ch.qos.logback + logback-classic + + + + + org.vaadin.artur + a-vaadin-helper + 1.5.0 + + + org.vaadin.artur.exampledata + exampledata + 3.0.0 + + + + org.springframework.boot + spring-boot-devtools + true + + + + com.vaadin + vaadin-testbench + test + ${project.version} + + + junit + junit + 4.13.1 + test + + + + org.slf4j + slf4j-simple + + + + + io.github.bonigarcia + webdrivermanager + 4.4.3 + test + + + + + + maven-clean-plugin + 3.1.0 + + + auto-clean + initialize + + clean + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + 500 + 240 + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.0.0 + + + parse-version + + parse-version + + + + + + maven-failsafe-plugin + 2.20 + + + + javax.xml.bind + jaxb-api + 2.2.11 + + + com.sun.xml.bind + jaxb-core + 2.2.11 + + + com.sun.xml.bind + jaxb-impl + 2.2.11 + + + javax.activation + activation + 1.1.1 + + + + + + integration-test + verify + + + + + false + + + + org.codehaus.mojo + properties-maven-plugin + 1.0.0 + + + read-local-properties-if-present + initialize + + read-project-properties + + + + ../local.properties + + true + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + true + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + + true + + + + com.vaadin + vaadin-maven-plugin + ${project.version} + + + + prepare-frontend + + + + + + maven-antrun-plugin + 3.0.0 + + + compile + + ${ce.license} + + + run + + + + + + + + + + ITs + + + !skipTests + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + repackage + + repackage + + + + start-spring-boot + pre-integration-test + + start + + + + stop-spring-boot + post-integration-test + + stop + + + + + com.vaadin.platform.fusion.test.Application + + + + + + + diff --git a/vaadin-platform-react-hybrid-test/pom.xml b/vaadin-platform-react-hybrid-test/pom.xml new file mode 100644 index 000000000..aefa0788e --- /dev/null +++ b/vaadin-platform-react-hybrid-test/pom.xml @@ -0,0 +1,293 @@ + + 4.0.0 + + com.vaadin + vaadin-platform-parent + 24.5-SNAPSHOT + + vaadin-platform-react-hybrid-test-prod + war + Vaadin Platform Hybrid (flow and fusion views) Tests (Production Mode) + Vaadin Platform Hybrid (flow and fusion views) Tests (Production Mode) + https://vaadin.com + + 17 + 17 + UTF-8 + UTF-8 + false + true + + + + + com.vaadin + vaadin-bom + ${project.version} + pom + import + + + org.springframework.boot + spring-boot-dependencies + ${spring.boot.version} + pom + import + + + + + + + Vaadin Directory + https://maven.vaadin.com/vaadin-addons + + false + + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + vaadin-prereleases + https://maven.vaadin.com/vaadin-prereleases + + + vaadin-addons + https://maven.vaadin.com/vaadin-addons + + + + + + com.vaadin + vaadin + + + com.vaadin + vaadin-spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + ch.qos.logback + logback-classic + + + + + + org.springframework.boot + spring-boot-devtools + true + + + + com.vaadin + vaadin-testbench + test + ${project.version} + + + junit + junit + 4.13.1 + test + + + + org.slf4j + slf4j-simple + + + + io.github.bonigarcia + webdrivermanager + 4.4.3 + test + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.0.0 + + + parse-version + + parse-version + + + + + + org.codehaus.mojo + properties-maven-plugin + 1.0.0 + + + read-local-properties-if-present + initialize + + read-project-properties + + + + ../local.properties + + true + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + true + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + + true + + + + com.vaadin + vaadin-maven-plugin + + + + prepare-frontend + + + + + + + + + + ITs + + + !skipTests + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + repackage + + repackage + + + + start-spring-boot + pre-integration-test + + start + + + + stop-spring-boot + post-integration-test + + stop + + + + + com.vaadin.platform.fusion.test.Application + + + + maven-failsafe-plugin + 2.20 + + + + javax.xml.bind + jaxb-api + 2.2.11 + + + com.sun.xml.bind + jaxb-core + 2.2.11 + + + com.sun.xml.bind + jaxb-impl + 2.2.11 + + + javax.activation + activation + 1.1.1 + + + + + + integration-test + verify + + + + + false + + + + + + + production + + + vaadin.productionMode + + + + + + com.vaadin + vaadin-maven-plugin + ${project.version} + + + + build-frontend + + + + + + + + + diff --git a/vaadin-platform-react-hybrid-test/src/main/frontend/index.html b/vaadin-platform-react-hybrid-test/src/main/frontend/index.html new file mode 100644 index 000000000..d36e59347 --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/main/frontend/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + +
+ + diff --git a/vaadin-platform-react-hybrid-test/src/main/frontend/themes/fusion-test/styles.css b/vaadin-platform-react-hybrid-test/src/main/frontend/themes/fusion-test/styles.css new file mode 100644 index 000000000..e69de29bb diff --git a/vaadin-platform-react-hybrid-test/src/main/frontend/themes/fusion-test/theme.json b/vaadin-platform-react-hybrid-test/src/main/frontend/themes/fusion-test/theme.json new file mode 100644 index 000000000..653b73c07 --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/main/frontend/themes/fusion-test/theme.json @@ -0,0 +1 @@ +{"lumoImports":["typography","color","badge"]} \ No newline at end of file diff --git a/vaadin-platform-react-hybrid-test/src/main/frontend/utils/css-utils.ts b/vaadin-platform-react-hybrid-test/src/main/frontend/utils/css-utils.ts new file mode 100644 index 000000000..74bb3479f --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/main/frontend/utils/css-utils.ts @@ -0,0 +1,23 @@ +import { DomModule } from "@polymer/polymer/lib/elements/dom-module"; +import { stylesFromTemplate } from "@polymer/polymer/lib/utils/style-gather"; +import { CSSResult, unsafeCSS } from "lit"; + +/** + * Utility function for importing style modules. This is a temporary + * solution until there is a standard solution available + * @see https://github.com/vaadin/vaadin-themable-mixin/issues/73 + * + * @param id the style module to import + */ +export const CSSModule = (id: string): CSSResult => { + const template: HTMLTemplateElement | null = DomModule.import( + id, + "template" + ) as HTMLTemplateElement; + const cssText = + template && + stylesFromTemplate(template, "") + .map((style: HTMLStyleElement) => style.textContent) + .join(" "); + return unsafeCSS(cssText); +}; diff --git a/vaadin-platform-react-hybrid-test/src/main/frontend/views/@index.tsx b/vaadin-platform-react-hybrid-test/src/main/frontend/views/@index.tsx new file mode 100644 index 000000000..7e1779061 --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/main/frontend/views/@index.tsx @@ -0,0 +1,25 @@ +import {Button, VerticalLayout} from "@vaadin/react-components"; +import type { ViewConfig } from "@vaadin/hilla-file-router/types.js"; +import {useNavigate} from "react-router-dom"; + +export const config: ViewConfig = { + menu: { + title: "root", + }, + flowLayout: false +}; + +/** + * Hilla view that is available publicly. + */ +export default function Public() { + const navigate = useNavigate(); + + return ( + +

This is the Hill index page.

+ + +
+ ); +} diff --git a/vaadin-platform-react-hybrid-test/src/main/frontend/views/flow/@index.tsx b/vaadin-platform-react-hybrid-test/src/main/frontend/views/flow/@index.tsx new file mode 100644 index 000000000..eee7dc1d3 --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/main/frontend/views/flow/@index.tsx @@ -0,0 +1,17 @@ +import type {ViewConfig} from "@vaadin/hilla-file-router/types.js"; + +export const config: ViewConfig = { + menu: { + exclude: true, + title: "ERROR!", + }, +}; + +export default function HelloHilla() { + return ( +
+
Place holder! This will render flow route as a flow layout will be requested!
+
Also tests Hilla menu exclude feature!
+
+ ); +} \ No newline at end of file diff --git a/vaadin-platform-react-hybrid-test/src/main/frontend/views/flow/hello-hilla.tsx b/vaadin-platform-react-hybrid-test/src/main/frontend/views/flow/hello-hilla.tsx new file mode 100644 index 000000000..01500a544 --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/main/frontend/views/flow/hello-hilla.tsx @@ -0,0 +1,25 @@ +import { Button, TextField, VerticalLayout } from "@vaadin/react-components"; +import type { ViewConfig } from "@vaadin/hilla-file-router/types.js"; +import { useState } from "react"; +import { Notification } from '@vaadin/react-components/Notification.js'; + +export const config: ViewConfig = { + menu: { + title: "Hello React in Flow Layout", + }, + title: "Hilla in Flow" +}; + +export default function HelloHilla() { + const [name, setName] = useState(""); + + return ( + + setName(e.detail.value)} /> + + + ) + ; +} \ No newline at end of file diff --git a/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/@index.tsx b/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/@index.tsx new file mode 100644 index 000000000..6f9aa0cc3 --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/@index.tsx @@ -0,0 +1,17 @@ +import type {ViewConfig} from "@vaadin/hilla-file-router/types.js"; + +export const config: ViewConfig = { + menu: { + exclude: true, + title: "ERROR!", + }, +}; + +export default function Hilla() { + return ( +
+ +
"Hilla root view for menu!"
+
+ ); +} \ No newline at end of file diff --git a/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/@layout.tsx b/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/@layout.tsx new file mode 100644 index 000000000..076e4e78c --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/@layout.tsx @@ -0,0 +1,56 @@ +import { + AppLayout, + DrawerToggle, + Icon, + SideNav, + SideNavItem +} from "@vaadin/react-components"; +import { Outlet, useLocation, useNavigate } from 'react-router-dom'; +import { createMenuItems, useViewConfig } from '@vaadin/hilla-file-router/runtime.js'; +import { effect, Signal, signal } from "@vaadin/hilla-react-signals"; + +const vaadin = window.Vaadin as { + documentTitleSignal: Signal; +}; +vaadin.documentTitleSignal = signal(""); +effect(() => { document.title = vaadin.documentTitleSignal.value; }); + +export default function Layout() { + const navigate = useNavigate(); + const location = useLocation(); + vaadin.documentTitleSignal.value = useViewConfig()?.title ?? ''; + + + return ( + +
+
+

Hybrid Example With Stateful Auth

+ navigate(path!)} + location={location}> + { + createMenuItems().filter(({to}) => { + console.log("+++", to); + return to.startsWith("hilla") || to.startsWith("/hilla"); + }).map(({ to, icon, title }) => ( + + {icon && } + {title} + + )) + } + +
+ +
+ + +

+ {vaadin.documentTitleSignal} +

+ + +
+ ); +} diff --git a/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/components.tsx b/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/components.tsx new file mode 100644 index 000000000..b1ffb6805 --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/components.tsx @@ -0,0 +1,315 @@ +import { + Accordion, + AccordionHeading, + AccordionPanel, + Avatar, + AvatarGroup, + Button, + Checkbox, + CheckboxGroup, + ComboBox, + ConfirmDialog, + ContextMenu, + DatePicker, + DateTimePicker, + Details, + DetailsSummary, + Dialog, + DrawerToggle, + EmailField, + FormLayout, + Grid, + GridColumn, + GridSelectionColumn, + GridSortColumn, + GridSorter, + GridTreeToggle, + HorizontalLayout, + Icon, + Iconset, + IntegerField, + Item, + ListBox, + LoginForm, + LoginOverlay, + MenuBar, + Message, + MessageInput, + MessageList, + MultiSelectComboBox, + Notification, + NumberField, + PasswordField, + Popover, + ProgressBar, RadioButton, RadioGroup, + Scroller, + Select, SideNav, SideNavItem, SplitLayout, Tab, + Tabs, TabSheet, TabSheetTab, + TextArea, + TextField, + TimePicker, + Tooltip, + Upload, + VerticalLayout, + VirtualList, + VirtualListItemModel +} from "@vaadin/react-components"; +import type { ViewConfig } from "@vaadin/hilla-file-router/types.js"; +import { + Board, + BoardRow, + Chart, + ChartSeries, + CookieConsent, + Crud, + CrudEditColumn, + GridPro, + GridProEditColumn, + RichTextEditor +} from "@vaadin/react-components-pro"; + +export const config: ViewConfig = { + menu: { + title: "React Components", + } +}; + +export default function Components() { + const openLoginOverlay = () => { + // @ts-ignore + document.getElementsByTagName("vaadin-login-overlay").item(0).opened = true; + } + + return ( + + + + summary +
accordion content
+
+
+ + + + + +
top aA
+
top B
+
top C
+
+ +
mid
+
+ +
low A
+ +
low B / A
+
low B / B
+
low B / C
+
low B / D
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Context Menu

+
+ + + + + + + + + + + + + + +
+ Summary +
Details
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

H1

H2

H3

H4

H5
+
H6
+
+ +
Header
+
DIV
+
Footer
+
+
+ + + Select an Item + Item one + Item two +
+ Item three + Item four +
+ + + + + + + + + + + + + + + + + + + + + + Main menu + Nav Item 1 + + Nav Item 2 + Nav Item 2 - + 1 + Nav Item 2 - + 2 + + + + +
+
+
+ + + +
Panel 1
+
+ +
Panel 2
+
+ +
Panel 3
+
+
+ + + Tab 1 + Tab 2 + Tab 3 + + + + + + + + + +
+ Notice
+ Content +
+
+ + + +
+ ); +} \ No newline at end of file diff --git a/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/hello-react.tsx b/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/hello-react.tsx new file mode 100644 index 000000000..f7f42ebf6 --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/hello-react.tsx @@ -0,0 +1,24 @@ +import { Button, TextField, VerticalLayout } from "@vaadin/react-components"; +import type { ViewConfig } from "@vaadin/hilla-file-router/types.js"; +import { useState } from "react"; +import { Notification } from '@vaadin/react-components/Notification.js'; + +export const config: ViewConfig = { + menu: { + title: "Hello World React", + } +}; + +export default function HelloReact() { + const [name, setName] = useState(""); + + return ( + + setName(e.detail.value)} /> + + + ); +} \ No newline at end of file diff --git a/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/Application.java b/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/Application.java new file mode 100644 index 000000000..856d62477 --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/Application.java @@ -0,0 +1,37 @@ +package com.vaadin.platform.react.test; + +import com.vaadin.flow.component.page.AppShellConfigurator; +import com.vaadin.flow.server.PWA; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.context.annotation.Bean; + +import com.vaadin.flow.server.auth.DefaultMenuAccessControl; +import com.vaadin.flow.server.auth.MenuAccessControl; +import com.vaadin.flow.theme.Theme; + +/** + * The entry point of the Spring Boot application. + * + * Use the * and some desktop browsers. + * + */ +@SpringBootApplication +@Theme(value = "fusion-test") +@PWA(name = "fusion-test", shortName = "fusion-test", offlineResources = {"images/logo.png"}) +public class Application extends SpringBootServletInitializer implements AppShellConfigurator { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + @Bean + public MenuAccessControl customMenuAccessControl() { + var menuAccessControl = new DefaultMenuAccessControl(); + menuAccessControl.setPopulateClientSideMenu( + MenuAccessControl.PopulateClientMenu.ALWAYS); + return menuAccessControl; + } +} diff --git a/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowHillaView.java b/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowHillaView.java new file mode 100644 index 000000000..814f9e544 --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowHillaView.java @@ -0,0 +1,31 @@ +package com.vaadin.platform.react.test.views; + +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.router.Menu; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; + +@Route("hilla/flow") +@PageTitle("Hello Hilla") +@Menu(title = "Flow in hilla") +public class FlowHillaView extends HorizontalLayout { + + private TextField name; + private Button sayHello; + + public FlowHillaView() { + setPadding(true); + setSpacing(true); + name = new TextField("Your name for Flow"); + sayHello = new Button("Say hello"); + add(name, sayHello); + setVerticalComponentAlignment(Alignment.END, name, sayHello); + sayHello.addClickListener(e -> { + Notification.show("Hello " + name.getValue()); + }); + } + +} diff --git a/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowLayout.java b/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowLayout.java new file mode 100644 index 000000000..153e0e706 --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowLayout.java @@ -0,0 +1,77 @@ +package com.vaadin.platform.react.test.views; + +import com.vaadin.flow.component.applayout.AppLayout; +import com.vaadin.flow.component.applayout.DrawerToggle; +import com.vaadin.flow.component.html.Footer; +import com.vaadin.flow.component.html.H1; +import com.vaadin.flow.component.html.Header; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.orderedlayout.Scroller; +import com.vaadin.flow.component.sidenav.SideNav; +import com.vaadin.flow.component.sidenav.SideNavItem; +import com.vaadin.flow.router.Layout; +import com.vaadin.flow.router.RoutePrefix; +import com.vaadin.flow.server.menu.MenuConfiguration; +import com.vaadin.flow.theme.lumo.LumoUtility; + +@Layout("/flow") +@RoutePrefix("flow") +public class FlowLayout extends AppLayout { + + private H1 viewTitle; + + public FlowLayout() { + setPrimarySection(Section.DRAWER); + addDrawerContent(); + addHeaderContent(); + } + + private void addHeaderContent() { + DrawerToggle toggle = new DrawerToggle(); + toggle.setAriaLabel("Menu toggle"); + + viewTitle = new H1(); + viewTitle.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE); + + addToNavbar(true, toggle, viewTitle); + } + + private void addDrawerContent() { + Span appName = new Span("Flow menu"); + appName.addClassNames(LumoUtility.FontWeight.SEMIBOLD, LumoUtility.FontSize.LARGE); + Header header = new Header(appName); + + Scroller scroller = new Scroller(createNavigation()); + + addToDrawer(header, scroller, createFooter()); + } + + private SideNav createNavigation() { + SideNav nav = new SideNav(); + + MenuConfiguration.getMenuEntries().forEach(menuEntry -> { + if(menuEntry.path().startsWith("flow") || menuEntry.path().startsWith("/flow")) { + nav.addItem( + new SideNavItem(menuEntry.title(), menuEntry.path())); + } + }); + + return nav; + } + + private Footer createFooter() { + Footer layout = new Footer(); + + return layout; + } + + @Override + protected void afterNavigation() { + super.afterNavigation(); + viewTitle.setText(getCurrentPageTitle()); + } + + private String getCurrentPageTitle() { + return MenuConfiguration.getPageHeader(getContent()).orElse(""); + } +} diff --git a/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowMainView.java b/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowMainView.java new file mode 100644 index 000000000..98300d2e9 --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowMainView.java @@ -0,0 +1,12 @@ +package com.vaadin.platform.react.test.views; + +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Route; + +@Route("flow") +public class FlowMainView extends VerticalLayout { + public FlowMainView() { + add(new Span("Flow root view for menu!")); + } +} diff --git a/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/HelloWorldView.java b/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/HelloWorldView.java new file mode 100644 index 000000000..c834b7b69 --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/HelloWorldView.java @@ -0,0 +1,31 @@ +package com.vaadin.platform.react.test.views; + +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.router.Menu; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; + +@Route("flow/hello-world") +@PageTitle("Hello World") +@Menu(title = "Flow Hello") +public class HelloWorldView extends HorizontalLayout { + + private TextField name; + private Button sayHello; + + public HelloWorldView() { + setPadding(true); + setSpacing(true); + name = new TextField("Your name"); + sayHello = new Button("Say hello"); + add(name, sayHello); + setVerticalComponentAlignment(Alignment.END, name, sayHello); + sayHello.addClickListener(e -> { + Notification.show("Hello " + name.getValue()); + }); + } + +} diff --git a/vaadin-platform-react-hybrid-test/src/main/resources/META-INF/resources/icons/icon.png b/vaadin-platform-react-hybrid-test/src/main/resources/META-INF/resources/icons/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6fde5c10d44ec14b621ef75e6b283f564c017f38 GIT binary patch literal 15994 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 + * This facilitates testing with network connection overrides, e. g., using + * offline mode in the tests. + *

+ * It is required to set system property with path to the driver to be able to + * run the test. + *

+ * The test can be executed locally and on a test Hub. ChromeDriver is used + * if test is executed locally. + * + * @author Vaadin Ltd + * @since 1.0 + * + */ +public abstract class ChromeDeviceTest extends ParallelTest { + public static final int SERVER_PORT = Integer + .parseInt(System.getProperty("serverPort", "8080")); + + @BeforeClass + public static void setupClass() { + String sauceKey = SauceLabsIntegration.getSauceAccessKey(); + String hubHost = System.getProperty("com.vaadin.testbench.Parameters.hubHostname"); + if ((sauceKey == null || sauceKey.isEmpty()) && (hubHost == null || hubHost.isEmpty())) { + String driver = System.getProperty("webdriver.chrome.driver"); + if (driver == null || !new File(driver).exists()) { + WebDriverManager.chromedriver().setup(); + } + } + } + + @Before + @Override + public void setup() throws Exception { + ChromeOptions chromeOptions = + customizeChromeOptions(new ChromeOptions()); + WebDriver driver; + // Always give priority to @RunLocally annotation + if ((getRunLocallyBrowser() != null)) { + driver = new ChromeDriver(chromeOptions); + } else if (Parameters.isLocalWebDriverUsed()) { + driver = new ChromeDriver(chromeOptions); + } else if (SauceLabsIntegration.isConfiguredForSauceLabs()) { + driver = new RemoteWebDriver(new URL(getHubURL()), + chromeOptions.merge(getDesiredCapabilities())); + } else if (getRunOnHub(getClass()) != null + || Parameters.getHubHostname() != null) { + driver = new RemoteWebDriver(new URL(getHubURL()), + chromeOptions.merge(getDesiredCapabilities())); + } else { + driver = new ChromeDriver(chromeOptions); + } + + setDriver(TestBench.createDriver(driver)); + } + + /** + * Customizes given Chrome options to enable network connection emulation. + * + * @param chromeOptions Chrome options to customize + * @return customized Chrome options instance + */ + protected ChromeOptions customizeChromeOptions(ChromeOptions chromeOptions) { + // Unfortunately using offline emulation ("setNetworkConnection" + // session command) in Chrome requires the "networkConnectionEnabled" + // capability, which is: + // - Not W3C WebDriver API compliant, so we disable W3C protocol + // - device mode: mobileEmulation option with some device settings + + final Map mobileEmulationParams = new HashMap<>(); + mobileEmulationParams.put("deviceName", "Laptop with touch"); + + chromeOptions.setExperimentalOption("w3c", false); + chromeOptions.setExperimentalOption("mobileEmulation", + mobileEmulationParams); + chromeOptions.setCapability("networkConnectionEnabled", true); + + + // Enable service workers over http remote connection + chromeOptions.addArguments(String.format( + "--unsafely-treat-insecure-origin-as-secure=%s", + getRootURL())); + + // NOTE: this flag is not supported in headless Chrome, see + // https://crbug.com/814146 + + // For test stability on Linux when not running headless. + // https://stackoverflow.com/questions/50642308/webdriverexception-unknown-error-devtoolsactiveport-file-doesnt-exist-while-t + chromeOptions.addArguments("--disable-dev-shm-usage"); + + return chromeOptions; + } + + /** + * Change network connection type in the browser. + * + * @param connectionType the new connection type + * @throws IOException + */ + protected void setConnectionType( + NetworkConnection.ConnectionType connectionType) + throws IOException { + RemoteWebDriver driver = (RemoteWebDriver) ((TestBenchDriverProxy) getDriver()) + .getWrappedDriver(); + final Map parameters = new HashMap<>(); + parameters.put("type", connectionType.hashCode()); + final Map connectionParams = new HashMap<>(); + connectionParams.put("parameters", parameters); + Response response = driver.getCommandExecutor() + .execute(new Command(driver.getSessionId(), + "setNetworkConnection", connectionParams)); + if (response.getStatus() != 0) { + throw new RuntimeException("Unable to set connection type"); + } + } + + public void waitForServiceWorkerReady() { + Assert.assertTrue("Should have navigator.serviceWorker", + (Boolean) executeScript("return !!navigator.serviceWorker;")); + + // Wait until service worker is ready + Assert.assertTrue("Should have service worker registered", + (Boolean) ((JavascriptExecutor) getDriver()).executeAsyncScript( + "const done = arguments[arguments.length - 1];" + + "const timeout = new Promise(" + + " resolve => setTimeout(resolve, 100000)" + + ");" + + "Promise.race([" + + " navigator.serviceWorker.ready," + + " timeout])" + + ".then(result => done(!!result));")); + } + + /** + * Returns the URL to the root of the server, e.g. "http://localhost:8888". + * + * @return the URL to the root + */ + protected String getRootURL() { + return "http://" + getDeploymentHostname() + ":" + getDeploymentPort(); + } + + /** + * Used to determine what port the test is running on. + * + * @return The port the test is running on, by default 8080 + */ + protected int getDeploymentPort() { + return SERVER_PORT; + } + + /** + * Used to determine what URL to initially open for the test. + * + * @return the host name of development server + */ + protected String getDeploymentHostname() { + return "localhost"; + } + + /** + * If dev server start in progress wait until it's started. Otherwise return + * immidiately. + */ + protected void waitForDevServer() { + Object result; + do { + getCommandExecutor().waitForVaadin(); + result = getCommandExecutor().executeScript( + "return window.Vaadin && window.Vaadin.Flow && window.Vaadin.Flow.devServerIsNotLoaded;"); + } while (Boolean.TRUE.equals(result)); + } + + @BrowserConfiguration + public List getBrowserConfiguration() { + return Collections + .singletonList(Browser.CHROME.getDesiredCapabilities()); + } + +} diff --git a/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ChromeOfflineIT.java b/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ChromeOfflineIT.java new file mode 100644 index 000000000..aa3fbb7d3 --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ChromeOfflineIT.java @@ -0,0 +1,84 @@ +/* + * Copyright 2000-2023 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.platform.react.offline; + +import java.io.IOException; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.logging.LogType; +import org.openqa.selenium.mobile.NetworkConnection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ChromeOfflineIT extends ChromeDeviceTest { + + protected Logger getLogger() { + return LoggerFactory.getLogger(this.getClass().getSimpleName()); + } + + @Test + public void offlineRoot_reload_viewReloaded() throws IOException { + getDriver().get(getRootURL() + "/"); + waitForDevServer(); + + // Confirm that app shell is loaded + Assert.assertNotNull("Should have outlet when loaded online", + findElement(By.id("outlet"))); + + // Confirm that client side view is loaded + Assert.assertNotNull("Should have in DOM when loaded online", + findElement(By.tagName("hello-world-ts-view"))); + + // Print in logger the browser console log + driver.manage().logs().get(LogType.BROWSER).getAll().stream() + .forEach(c -> getLogger().warn("Console - {} {}", c.getLevel(), c.getMessage())); + + waitForServiceWorkerReady(); + + // Set offline network conditions in ChromeDriver + setConnectionType(NetworkConnection.ConnectionType.AIRPLANE_MODE); + + try { + Assert.assertEquals("navigator.onLine should be false", false, + executeScript("return navigator.onLine")); + + // Reload the page in offline mode + executeScript("window.location.reload();"); + waitUntil(webDriver -> ((JavascriptExecutor) driver) + .executeScript("return document.readyState") + .equals("complete")); + + // Confirm that app shell is loaded + Assert.assertNotNull("Should have outlet when loaded offline", + findElement(By.id("outlet"))); + + // Confirm that client side view is loaded + Assert.assertNotNull("Should have in DOM when loaded offline", + findElement(By.tagName("hello-world-ts-view"))); + + // Confirm that connection lost indicator is visible + Assert.assertNotNull("Should have outlet when loaded offline", + findElement(By.tagName("vaadin-connection-indicator")).getAttribute("offline")); + } finally { + // Reset network conditions back + setConnectionType(NetworkConnection.ConnectionType.ALL); + } + } + +} diff --git a/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ComponentsIT.java b/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ComponentsIT.java new file mode 100644 index 000000000..9a12dbf8f --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ComponentsIT.java @@ -0,0 +1,75 @@ +/* + * Copyright 2000-2023 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.platform.react.offline; + +import static org.junit.Assert.assertNotNull; + +import java.util.List; +import java.util.Objects; +import java.util.logging.Level; +import java.util.stream.Collectors; + +import org.junit.Test; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.logging.LogEntry; +import org.openqa.selenium.logging.LogType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.vaadin.flow.component.login.testbench.LoginOverlayElement; +import com.vaadin.flow.component.notification.testbench.NotificationElement; + +public class ComponentsIT extends ChromeDeviceTest { + + protected Logger getLogger() { + return LoggerFactory.getLogger(this.getClass().getSimpleName()); + } + + @Test + public void loadComponents() throws Exception { + getDriver().get(getRootURL() + "/hilla/components"); + + // There should not be component errors in console + checkLogsForErrors(); + // Notification opens when loaded + $(NotificationElement.class).waitForFirst(); + + // Do something with the view + WebElement view = $("components-view").first().getWrappedElement(); + getCommandExecutor().executeScript("arguments[0].openLoginOverlay()", view); + assertNotNull($(LoginOverlayElement.class).waitForFirst()); + } + + private void checkLogsForErrors() { + getLogEntries(Level.WARNING).forEach(logEntry -> { + if ((Objects.equals(logEntry.getLevel(), Level.SEVERE) || logEntry.getMessage().contains("404"))) { + throw new AssertionError(String.format( + "Received error message in browser log console right after opening the page, message: %s", logEntry)); + } else { + getLogger().warn("This message in browser log console may be a potential error: '{}'", logEntry); + } + }); + } + + private List getLogEntries(Level level) { + getCommandExecutor().waitForVaadin(); + return driver.manage().logs().get(LogType.BROWSER).getAll().stream() + .filter(logEntry -> logEntry.getLevel().intValue() >= level.intValue()) + // exclude the favicon error + .filter(logEntry -> !logEntry.getMessage().contains("favicon.ico")).collect(Collectors.toList()); + } + +} From 90421e24e7cab064ec36574334131f52ae5cb4c0 Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Fri, 11 Oct 2024 14:45:31 +0300 Subject: [PATCH 02/14] update to new 24.6 --- vaadin-platform-react-hybrid-test/pom-dev-mode.xml | 2 +- vaadin-platform-react-hybrid-test/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vaadin-platform-react-hybrid-test/pom-dev-mode.xml b/vaadin-platform-react-hybrid-test/pom-dev-mode.xml index fa31a9fde..a82bcffa4 100644 --- a/vaadin-platform-react-hybrid-test/pom-dev-mode.xml +++ b/vaadin-platform-react-hybrid-test/pom-dev-mode.xml @@ -5,7 +5,7 @@ com.vaadin vaadin-platform-parent - 24.5-SNAPSHOT + 24.6-SNAPSHOT vaadin-platform-react-hybrid-test jar diff --git a/vaadin-platform-react-hybrid-test/pom.xml b/vaadin-platform-react-hybrid-test/pom.xml index aefa0788e..56e2d4394 100644 --- a/vaadin-platform-react-hybrid-test/pom.xml +++ b/vaadin-platform-react-hybrid-test/pom.xml @@ -5,7 +5,7 @@ com.vaadin vaadin-platform-parent - 24.5-SNAPSHOT + 24.6-SNAPSHOT vaadin-platform-react-hybrid-test-prod war From 5c928bd2bf510b8b2d83c0334954b133cf73cd78 Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Fri, 11 Oct 2024 15:06:18 +0300 Subject: [PATCH 03/14] react in name and description --- vaadin-platform-react-hybrid-test/pom-dev-mode.xml | 4 ++-- vaadin-platform-react-hybrid-test/pom.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vaadin-platform-react-hybrid-test/pom-dev-mode.xml b/vaadin-platform-react-hybrid-test/pom-dev-mode.xml index a82bcffa4..ab577c5c1 100644 --- a/vaadin-platform-react-hybrid-test/pom-dev-mode.xml +++ b/vaadin-platform-react-hybrid-test/pom-dev-mode.xml @@ -9,8 +9,8 @@ vaadin-platform-react-hybrid-test jar - Vaadin Platform Hybrid Tests (Dev Mode) - Vaadin Platform Hybrid Tests (Dev Mode) + Vaadin Platform React Hybrid Tests (Dev Mode) + Vaadin Platform React Hybrid Tests (Dev Mode) https://vaadin.com 17 diff --git a/vaadin-platform-react-hybrid-test/pom.xml b/vaadin-platform-react-hybrid-test/pom.xml index 56e2d4394..b92783e11 100644 --- a/vaadin-platform-react-hybrid-test/pom.xml +++ b/vaadin-platform-react-hybrid-test/pom.xml @@ -9,8 +9,8 @@ vaadin-platform-react-hybrid-test-prod war - Vaadin Platform Hybrid (flow and fusion views) Tests (Production Mode) - Vaadin Platform Hybrid (flow and fusion views) Tests (Production Mode) + Vaadin Platform React Hybrid (flow and hilla views) Tests (Production Mode) + Vaadin Platform React Hybrid (flow and hilla views) Tests (Production Mode) https://vaadin.com 17 From 209004f0f6efcd99fe01700912243f4c05fe0b80 Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Mon, 14 Oct 2024 07:58:24 +0300 Subject: [PATCH 04/14] fix application configuration packages. --- vaadin-platform-react-hybrid-test/pom-dev-mode.xml | 2 +- vaadin-platform-react-hybrid-test/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vaadin-platform-react-hybrid-test/pom-dev-mode.xml b/vaadin-platform-react-hybrid-test/pom-dev-mode.xml index ab577c5c1..7749291a3 100644 --- a/vaadin-platform-react-hybrid-test/pom-dev-mode.xml +++ b/vaadin-platform-react-hybrid-test/pom-dev-mode.xml @@ -309,7 +309,7 @@ - com.vaadin.platform.fusion.test.Application + com.vaadin.platform.react.test.Application diff --git a/vaadin-platform-react-hybrid-test/pom.xml b/vaadin-platform-react-hybrid-test/pom.xml index b92783e11..e633faa7f 100644 --- a/vaadin-platform-react-hybrid-test/pom.xml +++ b/vaadin-platform-react-hybrid-test/pom.xml @@ -220,7 +220,7 @@ - com.vaadin.platform.fusion.test.Application + com.vaadin.platform.react.test.Application From 689fd6466fbb11d42e194f244bb6471c97e1f634 Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Mon, 14 Oct 2024 08:24:15 +0300 Subject: [PATCH 05/14] Use abstractPlatformTest for test base --- .../react/offline/ChromeDeviceTest.java | 229 ------------------ .../react/offline/ChromeOfflineIT.java | 84 ------- .../react/test/AbstractPlatformTest.java | 95 ++++++++ .../react/{offline => test}/ComponentsIT.java | 14 +- 4 files changed, 101 insertions(+), 321 deletions(-) delete mode 100644 vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ChromeDeviceTest.java delete mode 100644 vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ChromeOfflineIT.java create mode 100644 vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/AbstractPlatformTest.java rename vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/{offline => test}/ComponentsIT.java (90%) diff --git a/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ChromeDeviceTest.java b/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ChromeDeviceTest.java deleted file mode 100644 index b1332cc3c..000000000 --- a/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ChromeDeviceTest.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2000-2023 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.platform.react.offline; - -import java.io.IOException; -import java.net.URL; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.io.File; - -import com.vaadin.testbench.Parameters; -import com.vaadin.testbench.TestBench; -import com.vaadin.testbench.TestBenchDriverProxy; -import com.vaadin.testbench.annotations.BrowserConfiguration; -import com.vaadin.testbench.parallel.Browser; -import com.vaadin.testbench.parallel.ParallelTest; -import com.vaadin.testbench.parallel.SauceLabsIntegration; - -import org.junit.Assert; -import org.junit.Before; -import io.github.bonigarcia.wdm.WebDriverManager; -import org.junit.BeforeClass; -import org.openqa.selenium.JavascriptExecutor; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.chrome.ChromeDriver; -import org.openqa.selenium.chrome.ChromeOptions; -import org.openqa.selenium.mobile.NetworkConnection; -import org.openqa.selenium.remote.Command; -import org.openqa.selenium.remote.DesiredCapabilities; -import org.openqa.selenium.remote.RemoteWebDriver; -import org.openqa.selenium.remote.Response; - -/** - * Base class for TestBench tests to run in Chrome with customized options, - * which enable device emulation mode by default. - *

- * This facilitates testing with network connection overrides, e. g., using - * offline mode in the tests. - *

- * It is required to set system property with path to the driver to be able to - * run the test. - *

- * The test can be executed locally and on a test Hub. ChromeDriver is used - * if test is executed locally. - * - * @author Vaadin Ltd - * @since 1.0 - * - */ -public abstract class ChromeDeviceTest extends ParallelTest { - public static final int SERVER_PORT = Integer - .parseInt(System.getProperty("serverPort", "8080")); - - @BeforeClass - public static void setupClass() { - String sauceKey = SauceLabsIntegration.getSauceAccessKey(); - String hubHost = System.getProperty("com.vaadin.testbench.Parameters.hubHostname"); - if ((sauceKey == null || sauceKey.isEmpty()) && (hubHost == null || hubHost.isEmpty())) { - String driver = System.getProperty("webdriver.chrome.driver"); - if (driver == null || !new File(driver).exists()) { - WebDriverManager.chromedriver().setup(); - } - } - } - - @Before - @Override - public void setup() throws Exception { - ChromeOptions chromeOptions = - customizeChromeOptions(new ChromeOptions()); - WebDriver driver; - // Always give priority to @RunLocally annotation - if ((getRunLocallyBrowser() != null)) { - driver = new ChromeDriver(chromeOptions); - } else if (Parameters.isLocalWebDriverUsed()) { - driver = new ChromeDriver(chromeOptions); - } else if (SauceLabsIntegration.isConfiguredForSauceLabs()) { - driver = new RemoteWebDriver(new URL(getHubURL()), - chromeOptions.merge(getDesiredCapabilities())); - } else if (getRunOnHub(getClass()) != null - || Parameters.getHubHostname() != null) { - driver = new RemoteWebDriver(new URL(getHubURL()), - chromeOptions.merge(getDesiredCapabilities())); - } else { - driver = new ChromeDriver(chromeOptions); - } - - setDriver(TestBench.createDriver(driver)); - } - - /** - * Customizes given Chrome options to enable network connection emulation. - * - * @param chromeOptions Chrome options to customize - * @return customized Chrome options instance - */ - protected ChromeOptions customizeChromeOptions(ChromeOptions chromeOptions) { - // Unfortunately using offline emulation ("setNetworkConnection" - // session command) in Chrome requires the "networkConnectionEnabled" - // capability, which is: - // - Not W3C WebDriver API compliant, so we disable W3C protocol - // - device mode: mobileEmulation option with some device settings - - final Map mobileEmulationParams = new HashMap<>(); - mobileEmulationParams.put("deviceName", "Laptop with touch"); - - chromeOptions.setExperimentalOption("w3c", false); - chromeOptions.setExperimentalOption("mobileEmulation", - mobileEmulationParams); - chromeOptions.setCapability("networkConnectionEnabled", true); - - - // Enable service workers over http remote connection - chromeOptions.addArguments(String.format( - "--unsafely-treat-insecure-origin-as-secure=%s", - getRootURL())); - - // NOTE: this flag is not supported in headless Chrome, see - // https://crbug.com/814146 - - // For test stability on Linux when not running headless. - // https://stackoverflow.com/questions/50642308/webdriverexception-unknown-error-devtoolsactiveport-file-doesnt-exist-while-t - chromeOptions.addArguments("--disable-dev-shm-usage"); - - return chromeOptions; - } - - /** - * Change network connection type in the browser. - * - * @param connectionType the new connection type - * @throws IOException - */ - protected void setConnectionType( - NetworkConnection.ConnectionType connectionType) - throws IOException { - RemoteWebDriver driver = (RemoteWebDriver) ((TestBenchDriverProxy) getDriver()) - .getWrappedDriver(); - final Map parameters = new HashMap<>(); - parameters.put("type", connectionType.hashCode()); - final Map connectionParams = new HashMap<>(); - connectionParams.put("parameters", parameters); - Response response = driver.getCommandExecutor() - .execute(new Command(driver.getSessionId(), - "setNetworkConnection", connectionParams)); - if (response.getStatus() != 0) { - throw new RuntimeException("Unable to set connection type"); - } - } - - public void waitForServiceWorkerReady() { - Assert.assertTrue("Should have navigator.serviceWorker", - (Boolean) executeScript("return !!navigator.serviceWorker;")); - - // Wait until service worker is ready - Assert.assertTrue("Should have service worker registered", - (Boolean) ((JavascriptExecutor) getDriver()).executeAsyncScript( - "const done = arguments[arguments.length - 1];" - + "const timeout = new Promise(" - + " resolve => setTimeout(resolve, 100000)" - + ");" - + "Promise.race([" - + " navigator.serviceWorker.ready," - + " timeout])" - + ".then(result => done(!!result));")); - } - - /** - * Returns the URL to the root of the server, e.g. "http://localhost:8888". - * - * @return the URL to the root - */ - protected String getRootURL() { - return "http://" + getDeploymentHostname() + ":" + getDeploymentPort(); - } - - /** - * Used to determine what port the test is running on. - * - * @return The port the test is running on, by default 8080 - */ - protected int getDeploymentPort() { - return SERVER_PORT; - } - - /** - * Used to determine what URL to initially open for the test. - * - * @return the host name of development server - */ - protected String getDeploymentHostname() { - return "localhost"; - } - - /** - * If dev server start in progress wait until it's started. Otherwise return - * immidiately. - */ - protected void waitForDevServer() { - Object result; - do { - getCommandExecutor().waitForVaadin(); - result = getCommandExecutor().executeScript( - "return window.Vaadin && window.Vaadin.Flow && window.Vaadin.Flow.devServerIsNotLoaded;"); - } while (Boolean.TRUE.equals(result)); - } - - @BrowserConfiguration - public List getBrowserConfiguration() { - return Collections - .singletonList(Browser.CHROME.getDesiredCapabilities()); - } - -} diff --git a/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ChromeOfflineIT.java b/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ChromeOfflineIT.java deleted file mode 100644 index aa3fbb7d3..000000000 --- a/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ChromeOfflineIT.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2000-2023 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.platform.react.offline; - -import java.io.IOException; - -import org.junit.Assert; -import org.junit.Test; -import org.openqa.selenium.By; -import org.openqa.selenium.JavascriptExecutor; -import org.openqa.selenium.logging.LogType; -import org.openqa.selenium.mobile.NetworkConnection; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ChromeOfflineIT extends ChromeDeviceTest { - - protected Logger getLogger() { - return LoggerFactory.getLogger(this.getClass().getSimpleName()); - } - - @Test - public void offlineRoot_reload_viewReloaded() throws IOException { - getDriver().get(getRootURL() + "/"); - waitForDevServer(); - - // Confirm that app shell is loaded - Assert.assertNotNull("Should have outlet when loaded online", - findElement(By.id("outlet"))); - - // Confirm that client side view is loaded - Assert.assertNotNull("Should have in DOM when loaded online", - findElement(By.tagName("hello-world-ts-view"))); - - // Print in logger the browser console log - driver.manage().logs().get(LogType.BROWSER).getAll().stream() - .forEach(c -> getLogger().warn("Console - {} {}", c.getLevel(), c.getMessage())); - - waitForServiceWorkerReady(); - - // Set offline network conditions in ChromeDriver - setConnectionType(NetworkConnection.ConnectionType.AIRPLANE_MODE); - - try { - Assert.assertEquals("navigator.onLine should be false", false, - executeScript("return navigator.onLine")); - - // Reload the page in offline mode - executeScript("window.location.reload();"); - waitUntil(webDriver -> ((JavascriptExecutor) driver) - .executeScript("return document.readyState") - .equals("complete")); - - // Confirm that app shell is loaded - Assert.assertNotNull("Should have outlet when loaded offline", - findElement(By.id("outlet"))); - - // Confirm that client side view is loaded - Assert.assertNotNull("Should have in DOM when loaded offline", - findElement(By.tagName("hello-world-ts-view"))); - - // Confirm that connection lost indicator is visible - Assert.assertNotNull("Should have outlet when loaded offline", - findElement(By.tagName("vaadin-connection-indicator")).getAttribute("offline")); - } finally { - // Reset network conditions back - setConnectionType(NetworkConnection.ConnectionType.ALL); - } - } - -} diff --git a/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/AbstractPlatformTest.java b/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/AbstractPlatformTest.java new file mode 100644 index 000000000..d68c5405c --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/AbstractPlatformTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2000-2023 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.platform.react.test; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.vaadin.testbench.IPAddress; +import com.vaadin.testbench.parallel.ParallelTest; +import com.vaadin.testbench.parallel.SauceLabsIntegration; + +public abstract class AbstractPlatformTest extends ParallelTest { + + public static final int SERVER_PORT = Integer + .parseInt(System.getProperty("serverPort", "8080")); + + static String hostName; + static boolean isSauce; + static boolean isHub; + static boolean isLocal; + + static Logger getLogger() { + return LoggerFactory.getLogger(AbstractPlatformTest.class); + } + + @BeforeClass + public static void setupClass() { + isSauce = SauceLabsIntegration.isConfiguredForSauceLabs(); + String hubHost = System + .getProperty("com.vaadin.testbench.Parameters.hubHostname"); + isHub = !isSauce && hubHost != null && !hubHost.isEmpty(); + + hostName = isHub ? IPAddress.findSiteLocalAddress() : "localhost"; + getLogger().info("Running Tests app-url=http://{}:{} mode={}", hostName, + SERVER_PORT, + isSauce ? "SAUCE (user:" + SauceLabsIntegration.getSauceUser() + ")" + : isHub ? "HUB (hub-host:" + hubHost + ")" + : "LOCAL (chromedriver)"); + } + + @Before + public void setUp() { + getDriver().get(getRootURL() + getTestPath()); + } + + /** + * Gets the absolute path to the test, starting with a "/". + * + * @return he path to the test, appended to {@link #getRootURL()} for the + * full test URL. + */ + protected abstract String getTestPath(); + + /** + * Returns the URL to the root of the server, e.g. "http://localhost:8888". + * + * @return the URL to the root + */ + protected String getRootURL() { + return "http://" + getDeploymentHostname() + ":" + getDeploymentPort(); + } + + /** + * Used to determine what port the test is running on. + * + * @return The port the test is running on, by default 8080 + */ + protected int getDeploymentPort() { + return SERVER_PORT; + } + + /** + * Used to determine what URL to initially open for the test. + * + * @return the host name of development server + */ + protected String getDeploymentHostname() { + return hostName; + } +} diff --git a/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ComponentsIT.java b/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/ComponentsIT.java similarity index 90% rename from vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ComponentsIT.java rename to vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/ComponentsIT.java index 9a12dbf8f..ed25d51ae 100644 --- a/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/offline/ComponentsIT.java +++ b/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/ComponentsIT.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ -package com.vaadin.platform.react.offline; +package com.vaadin.platform.react.test; import static org.junit.Assert.assertNotNull; @@ -32,16 +32,10 @@ import com.vaadin.flow.component.login.testbench.LoginOverlayElement; import com.vaadin.flow.component.notification.testbench.NotificationElement; -public class ComponentsIT extends ChromeDeviceTest { - - protected Logger getLogger() { - return LoggerFactory.getLogger(this.getClass().getSimpleName()); - } +public class ComponentsIT extends AbstractPlatformTest { @Test public void loadComponents() throws Exception { - getDriver().get(getRootURL() + "/hilla/components"); - // There should not be component errors in console checkLogsForErrors(); // Notification opens when loaded @@ -72,4 +66,8 @@ private List getLogEntries(Level level) { .filter(logEntry -> !logEntry.getMessage().contains("favicon.ico")).collect(Collectors.toList()); } + @Override + protected String getTestPath() { + return "/hilla/components"; + } } From 535c1c77efc32891e52f7a58951f9272713ca1ae Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Mon, 14 Oct 2024 09:22:02 +0300 Subject: [PATCH 06/14] Remove log, fix test --- .../src/main/frontend/views/hilla/@layout.tsx | 1 - .../src/main/frontend/views/hilla/components.tsx | 4 ++-- .../java/com/vaadin/platform/react/test/ComponentsIT.java | 8 ++++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/@layout.tsx b/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/@layout.tsx index 076e4e78c..121246ee0 100644 --- a/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/@layout.tsx +++ b/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/@layout.tsx @@ -31,7 +31,6 @@ export default function Layout() { location={location}> { createMenuItems().filter(({to}) => { - console.log("+++", to); return to.startsWith("hilla") || to.startsWith("/hilla"); }).map(({ to, icon, title }) => ( diff --git a/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/components.tsx b/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/components.tsx index b1ffb6805..0e51a8d93 100644 --- a/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/components.tsx +++ b/vaadin-platform-react-hybrid-test/src/main/frontend/views/hilla/components.tsx @@ -81,7 +81,7 @@ export default function Components() { } return ( - + summary @@ -237,7 +237,7 @@ export default function Components() { - diff --git a/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/ComponentsIT.java b/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/ComponentsIT.java index ed25d51ae..1ab2e15b5 100644 --- a/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/ComponentsIT.java +++ b/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/ComponentsIT.java @@ -29,8 +29,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.vaadin.flow.component.button.testbench.ButtonElement; import com.vaadin.flow.component.login.testbench.LoginOverlayElement; import com.vaadin.flow.component.notification.testbench.NotificationElement; +import com.vaadin.flow.component.orderedlayout.testbench.VerticalLayoutElement; public class ComponentsIT extends AbstractPlatformTest { @@ -42,8 +44,10 @@ public void loadComponents() throws Exception { $(NotificationElement.class).waitForFirst(); // Do something with the view - WebElement view = $("components-view").first().getWrappedElement(); - getCommandExecutor().executeScript("arguments[0].openLoginOverlay()", view); + VerticalLayoutElement mainLayout = $(VerticalLayoutElement.class).id( + "components"); + mainLayout.$(ButtonElement.class).id("open-overlay").click(); + assertNotNull($(LoginOverlayElement.class).waitForFirst()); } From 9604ac6fd12b807222a256a76e3ff08dfc3a6880 Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Mon, 14 Oct 2024 11:56:33 +0300 Subject: [PATCH 07/14] Flow to hilla navigation. --- .../react/test/views/FlowHillaView.java | 1 + .../react/test/views/FlowMainView.java | 4 ++- .../platform/react/test/FlowInHillaIT.java | 31 +++++++++++++++++++ .../platform/react/test/HillaInFlowIT.java | 31 +++++++++++++++++++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/FlowInHillaIT.java create mode 100644 vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/HillaInFlowIT.java diff --git a/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowHillaView.java b/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowHillaView.java index 814f9e544..605e9e314 100644 --- a/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowHillaView.java +++ b/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowHillaView.java @@ -17,6 +17,7 @@ public class FlowHillaView extends HorizontalLayout { private Button sayHello; public FlowHillaView() { + setId("flow-hilla"); setPadding(true); setSpacing(true); name = new TextField("Your name for Flow"); diff --git a/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowMainView.java b/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowMainView.java index 98300d2e9..89c415976 100644 --- a/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowMainView.java +++ b/vaadin-platform-react-hybrid-test/src/main/java/com/vaadin/platform/react/test/views/FlowMainView.java @@ -7,6 +7,8 @@ @Route("flow") public class FlowMainView extends VerticalLayout { public FlowMainView() { - add(new Span("Flow root view for menu!")); + Span span = new Span("Flow root view for menu!"); + span.setId("flow-main"); + add(span); } } diff --git a/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/FlowInHillaIT.java b/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/FlowInHillaIT.java new file mode 100644 index 000000000..f0b1701de --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/FlowInHillaIT.java @@ -0,0 +1,31 @@ +package com.vaadin.platform.react.test; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.support.ui.ExpectedConditions; + +import com.vaadin.flow.component.sidenav.testbench.SideNavItemElement; + +public class FlowInHillaIT extends AbstractPlatformTest{ + + @Test + public void flowViewInHillaLayout() { + Assert.assertNotNull(findElement(By.id("hilla"))); + + // Navigate to Flow view + $(SideNavItemElement.class).withCaption("Flow in hilla").first().click(); + + waitUntil(ExpectedConditions.presenceOfElementLocated(By.id("flow-hilla"))); + + Assert.assertNull("Showing hilla placeholder even though Flow should be shown", findElement(By.id("placeholder"))); + + // navigate away from Flow view + $(SideNavItemElement.class).withCaption("React Components").first().click(); + } + + @Override + protected String getTestPath() { + return "/hilla"; + } +} diff --git a/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/HillaInFlowIT.java b/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/HillaInFlowIT.java new file mode 100644 index 000000000..ce50a685c --- /dev/null +++ b/vaadin-platform-react-hybrid-test/src/test/java/com/vaadin/platform/react/test/HillaInFlowIT.java @@ -0,0 +1,31 @@ +package com.vaadin.platform.react.test; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.support.ui.ExpectedConditions; + +import com.vaadin.flow.component.sidenav.testbench.SideNavItemElement; + +public class HillaInFlowIT extends AbstractPlatformTest{ + + @Test + public void hillaViewInFlowLayout() { + waitUntil(ExpectedConditions.presenceOfElementLocated(By.id("flow-main"))); + + // Navigate to Flow view + $(SideNavItemElement.class).withCaption("Flow in hilla").first().click(); + + waitUntil(ExpectedConditions.presenceOfElementLocated(By.id("flow-hilla"))); + + Assert.assertNull("Showing hilla placeholder even though Flow should be shown", findElement(By.id("placeholder"))); + + // navigate away from Flow view + $(SideNavItemElement.class).withCaption("React Components").first().click(); + } + + @Override + protected String getTestPath() { + return "/flow"; + } +} From f322b92c5368da72383fd8f38f55f211e96960c4 Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Mon, 14 Oct 2024 12:11:05 +0300 Subject: [PATCH 08/14] fix menu find --- .../src/main/frontend/views/flow/hello-hilla.tsx | 2 +- .../platform/react/test/AbstractPlatformTest.java | 13 +++++++++++++ .../vaadin/platform/react/test/FlowInHillaIT.java | 13 +++++++++---- .../vaadin/platform/react/test/HillaInFlowIT.java | 9 +++++---- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/vaadin-platform-react-hybrid-test/src/main/frontend/views/flow/hello-hilla.tsx b/vaadin-platform-react-hybrid-test/src/main/frontend/views/flow/hello-hilla.tsx index 01500a544..0910f8214 100644 --- a/vaadin-platform-react-hybrid-test/src/main/frontend/views/flow/hello-hilla.tsx +++ b/vaadin-platform-react-hybrid-test/src/main/frontend/views/flow/hello-hilla.tsx @@ -14,7 +14,7 @@ export default function HelloHilla() { const [name, setName] = useState(""); return ( - + setName(e.detail.value)} />