diff --git a/flow-server/src/main/java/com/vaadin/flow/server/BootstrapHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/BootstrapHandler.java index 58585c6c475..4b07a1bc894 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/BootstrapHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/BootstrapHandler.java @@ -1178,6 +1178,15 @@ private void setupPwa(Document document, BootstrapContext context) { head.appendElement(META_TAG) .attr("name", "apple-mobile-web-app-capable") .attr(CONTENT_ATTRIBUTE, "yes"); + head.appendElement(META_TAG) + .attr("name", "mobile-web-app-capable") + .attr(CONTENT_ATTRIBUTE, "yes"); + head.appendElement(META_TAG) + .attr("name", "apple-touch-fullscreen") + .attr(CONTENT_ATTRIBUTE, "yes"); + head.appendElement(META_TAG) + .attr("name", "apple-mobile-web-app-title") + .attr(CONTENT_ATTRIBUTE, config.getShortName()); // Theme color head.appendElement(META_TAG).attr("name", "theme-color") diff --git a/flow-server/src/main/java/com/vaadin/flow/server/PwaIcon.java b/flow-server/src/main/java/com/vaadin/flow/server/PwaIcon.java index 7d400ad9eb0..fdd72376e74 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/PwaIcon.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/PwaIcon.java @@ -191,6 +191,15 @@ public String getType() { return attributes.get("type"); } + /** + * Gets the value of the {@literal rel} attribute. + * + * @return value of the {@literal rel} attribute + */ + String getRel() { + return attributes.get("rel"); + } + /** * Gets the icon {@link Domain}. * diff --git a/flow-server/src/main/java/com/vaadin/flow/server/PwaRegistry.java b/flow-server/src/main/java/com/vaadin/flow/server/PwaRegistry.java index 93dacdf7ab5..e89b5bd851e 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/PwaRegistry.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/PwaRegistry.java @@ -61,8 +61,10 @@ public class PwaRegistry implements Serializable { private static final String META_INF_RESOURCES = "/META-INF/resources"; private static final String HEADLESS_PROPERTY = "java.awt.headless"; private static final String APPLE_STARTUP_IMAGE = "apple-touch-startup-image"; - private static final String APPLE_IMAGE_MEDIA = "(device-width: %dpx) and (device-height: %dpx) " - + "and (-webkit-device-pixel-ratio: %d)"; + private static final String APPLE_IMAGE_MEDIA = "screen and (device-width: %dpx) and (device-height: %dpx)" + + " and (-webkit-device-pixel-ratio: %d) and (orientation: %s)"; + private static final String ORIENTATION_PORTRAIT = "portrait"; + private static final String ORIENTATION_LANDSCAPE = "landscape"; public static final String WORKBOX_FOLDER = "VAADIN/static/server/workbox/"; private static final String WORKBOX_CACHE_FORMAT = "{ url: '%s', revision: '%s' }"; @@ -494,25 +496,124 @@ private static List getIconTemplates(String baseName) { "apple-touch-icon", "")); // IOS device specific splash screens - // iPhone X (1125px x 2436px) + // iPad Pro 12.9 Portrait: + icons.add(new PwaIcon(2048, 2732, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 1024, 1366, 2, ORIENTATION_PORTRAIT))); + // iPad Pro 12.9 Landscape: + icons.add(new PwaIcon(2732, 2048, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 1024, 1366, 2, ORIENTATION_LANDSCAPE))); + + // iPad Pro 11, 10.5 Portrait: + icons.add(new PwaIcon(1668, 2388, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 834, 1194, 2, ORIENTATION_PORTRAIT))); + // iPad Pro 11, 10.5 Landscape: + icons.add(new PwaIcon(2388, 1668, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 834, 1194, 2, ORIENTATION_LANDSCAPE))); + + // iPad Air 10.5 Portrait: + icons.add(new PwaIcon(1668, 2224, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 834, 1112, 2, ORIENTATION_PORTRAIT))); + // iPad Air 10.5 Landscape: + icons.add(new PwaIcon(2224, 1668, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 834, 1112, 2, ORIENTATION_LANDSCAPE))); + + // iPad 10.2 Portrait: + icons.add(new PwaIcon(1620, 2160, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 768, 1024, 2, ORIENTATION_PORTRAIT))); + // iPad 10.2 Landscape: + icons.add(new PwaIcon(2160, 1620, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 768, 1024, 2, ORIENTATION_LANDSCAPE))); + + // iPad Pro 9.7, iPad Air 9.7, iPad 9.7, iPad mini 7.9 portrait + icons.add(new PwaIcon(1536, 2048, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 768, 1024, 2, ORIENTATION_PORTRAIT))); + // iPad Pro 9.7, iPad Air 9.7, iPad 9.7, iPad mini 7.9 landscape + icons.add(new PwaIcon(2048, 1536, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 768, 1024, 2, ORIENTATION_LANDSCAPE))); + + // iPhone 13 Pro Max, iPhone 12 Pro Max portrait + icons.add(new PwaIcon(1284, 2778, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 428, 926, 3, ORIENTATION_PORTRAIT))); + // iPhone 13 Pro Max, iPhone 12 Pro Max landscape + icons.add(new PwaIcon(2778, 1284, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 428, 926, 3, ORIENTATION_LANDSCAPE))); + + // iPhone 13 Pro, iPhone 13, iPhone 12 Pro, iPhone 12 portrait + icons.add(new PwaIcon(1170, 2532, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 390, 844, 3, ORIENTATION_PORTRAIT))); + // iPhone 13 Pro, iPhone 13, iPhone 12 Pro, iPhone 12 landscape + icons.add(new PwaIcon(2532, 1170, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 390, 844, 3, ORIENTATION_LANDSCAPE))); + + // iPhone 13 Mini, iPhone 12 Mini, iPhone 11 Pro, iPhone XS, iPhone X + // portrait icons.add(new PwaIcon(1125, 2436, baseName, PwaIcon.Domain.HEADER, - false, APPLE_STARTUP_IMAGE, - String.format(APPLE_IMAGE_MEDIA, 375, 812, 3))); - - // iPhone 8, 7, 6s, 6 (750px x 1334px) - icons.add(new PwaIcon(750, 1334, baseName, PwaIcon.Domain.HEADER, false, - APPLE_STARTUP_IMAGE, - String.format(APPLE_IMAGE_MEDIA, 375, 667, 2))); - - // iPhone 8 Plus, 7 Plus, 6s Plus, 6 Plus (1242px x 2208px) + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 375, 812, 3, ORIENTATION_PORTRAIT))); + // iPhone 13 Mini, iPhone 12 Mini, iPhone 11 Pro, iPhone XS, iPhone X + // landscape + icons.add(new PwaIcon(2436, 1125, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 375, 812, 3, ORIENTATION_LANDSCAPE))); + + // iPhone 11 Pro Max, iPhone XS Max portrait + icons.add(new PwaIcon(1242, 2688, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 414, 896, 3, ORIENTATION_PORTRAIT))); + // iPhone 11 Pro Max, iPhone XS Max landscape + icons.add(new PwaIcon(2688, 1242, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 414, 896, 3, ORIENTATION_LANDSCAPE))); + + // iPhone 11, iPhone XR portrait + icons.add(new PwaIcon(828, 1792, baseName, PwaIcon.Domain.HEADER, false, + APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, 414, 896, + 2, ORIENTATION_PORTRAIT))); + // iPhone 11, iPhone XR landscape + icons.add(new PwaIcon(1792, 828, baseName, PwaIcon.Domain.HEADER, false, + APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, 414, 896, + 2, ORIENTATION_LANDSCAPE))); + + // iPhone 8 Plus, 7 Plus, 6s Plus, 6 Plus portrait icons.add(new PwaIcon(1242, 2208, baseName, PwaIcon.Domain.HEADER, - false, APPLE_STARTUP_IMAGE, - String.format(APPLE_IMAGE_MEDIA, 414, 763, 3))); - - // iPhone 5 (640px x 1136px) + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 414, 736, 3, ORIENTATION_PORTRAIT))); + // iPhone 8 Plus, 7 Plus, 6s Plus, 6 Plus landscape + icons.add(new PwaIcon(2208, 1242, baseName, PwaIcon.Domain.HEADER, + false, APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, + 414, 736, 3, ORIENTATION_LANDSCAPE))); + + // iPhone 8, 7, 6s, 6, SE 4.7 portrait + icons.add(new PwaIcon(750, 1334, baseName, PwaIcon.Domain.HEADER, false, + APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, 375, 667, + 2, ORIENTATION_PORTRAIT))); + // iPhone 8, 7, 6s, 6, SE 4.7 landscape + icons.add(new PwaIcon(1334, 750, baseName, PwaIcon.Domain.HEADER, false, + APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, 375, 667, + 2, ORIENTATION_LANDSCAPE))); + + // iPhone 5, SE 4, iPod touch 5th Gen and later portrait icons.add(new PwaIcon(640, 1136, baseName, PwaIcon.Domain.HEADER, false, - APPLE_STARTUP_IMAGE, - String.format(APPLE_IMAGE_MEDIA, 320, 568, 2))); + APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, 320, 568, + 2, ORIENTATION_PORTRAIT))); + // iPhone 5, SE 4, iPod touch 5th Gen and later landscape + icons.add(new PwaIcon(1136, 640, baseName, PwaIcon.Domain.HEADER, false, + APPLE_STARTUP_IMAGE, String.format(APPLE_IMAGE_MEDIA, 320, 568, + 2, ORIENTATION_LANDSCAPE))); return icons; } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/BootstrapHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/BootstrapHandlerTest.java index 47224774ec2..09e6c115bae 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/BootstrapHandlerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/BootstrapHandlerTest.java @@ -684,28 +684,27 @@ public void page_configurator_append_inline_form_files() Document page = pageBuilder.getBootstrapPage(new BootstrapContext( request, null, session, testUI, this::contextRootRelativePath)); - Elements allElements = page.head().getAllElements(); - // Note element 0 is the full head element. - assertStringEquals( + String scripts = page.getElementsByTag("script").toString(); + + Assert.assertTrue( "File javascript should have been appended to head element", - "", - allElements.get(allElements.size() - 3).toString()); - assertStringEquals( - "File html should have been appended to head element", - "")); + Assert.assertTrue("File html should have been appended to head element", + scripts.contains("", - allElements.get(allElements.size() - 2).toString()); - assertStringEquals("File css should have been appended to head element", - "", - allElements.get(allElements.size() - 1).toString()); + + " color: rgba(255, 255, 0, 1);\n" + "}")); } @Test // 3036 @@ -1035,13 +1034,10 @@ public void force_wrapping_of_file() Document page = pageBuilder.getBootstrapPage(new BootstrapContext( request, null, session, testUI, this::contextRootRelativePath)); - Elements allElements = page.head().getAllElements(); - - assertStringEquals( - "File css should have been prepended to body element", - "", - allElements.get(allElements.size() - 1).toString()); + assertTrue("File css should have been prepended to body element", + page.getElementsByTag("style").toString().contains( + "")); } @Test // 3197 diff --git a/flow-server/src/test/java/com/vaadin/flow/server/PwaRegistryTest.java b/flow-server/src/test/java/com/vaadin/flow/server/PwaRegistryTest.java index 201fc2f417e..198f2a93daa 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/PwaRegistryTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/PwaRegistryTest.java @@ -16,19 +16,49 @@ package com.vaadin.flow.server; import javax.servlet.ServletContext; - import java.io.ByteArrayOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; import org.mockito.Mockito; @PWA(name = "foo", shortName = "bar") public class PwaRegistryTest { + @PWA(name = "Custom Icon Path", shortName = "CIP", iconPath = "icons/splash/foo.png") + private static class PwaWithCustomIconPath { + } + + private static List splashIconsForAppleDevices; + + @BeforeClass + public static void initPwaWithCustomIconPath() { + ServletContext context = Mockito.mock(ServletContext.class); + PwaRegistry registry = null; + try { + // Reflection is used here, because PwaRegistry has a private + // constructor and the 'getInstance' method is really hard to + // mocked up, it requires an access to + // 'ApplicationRouteRegistryWrapper', which is protected and + // invisible here. + registry = createPwaRegistryInstance( + PwaWithCustomIconPath.class.getAnnotation(PWA.class), + context); + } catch (Exception e) { + Assert.fail("Failed to create an instance of PwaRegistry: " + + e.getMessage()); + } + splashIconsForAppleDevices = registry.getIcons().stream().filter( + icon -> "apple-touch-startup-image".equals(icon.getRel())) + .collect(Collectors.toList()); + } + @Test public void pwaIconIsGeneratedBasedOnClasspathIcon_servletContextHasNoResources() { ServletContext context = Mockito.mock(ServletContext.class); @@ -63,7 +93,199 @@ public void pwaIconIsGeneratedBasedOnClasspathIcon_servletContextHasNoResources( Assert.assertEquals(26, stream.toByteArray()[36]); } - private PwaRegistry createPwaRegistryInstance(PWA pwa, + @Test + public void pwaWithCustomBaseIconPath_splashScreenIconForAllSupportedAppleDevicesAndOrientationsAreGenerated() { + Assert.assertEquals(26, splashIconsForAppleDevices.size()); + } + + @Test + public void pwaWithCustomBaseIconPath_splashScreenIconForAppleDevices_areGeneratedBasedOnIconPath() { + boolean customBaseNameUsedInIconGeneration = splashIconsForAppleDevices + .stream().allMatch( + icon -> icon.getHref().startsWith("icons/splash/foo")); + Assert.assertTrue(customBaseNameUsedInIconGeneration); + } + + @Test + public void pwaWithCustomBaseIconPath_splashScreenIconForIPadDevices_includeBothOrientations() { + // iPad Pro 12.9 + Predicate iPadPro129 = icon -> (icon.getWidth() == 2048 + && icon.getHeight() == 2732) + || (icon.getWidth() == 2732 && icon.getHeight() == 2048); + List mediaQueriesForIPadPro129 = splashIconsForAppleDevices + .stream().filter(iPadPro129) + .map(icon -> icon.asElement().attr("media")) + .collect(Collectors.toList()); + Assert.assertEquals(1, mediaQueriesForIPadPro129.stream() + .filter(media -> media.contains("portrait")).count()); + Assert.assertEquals(1, mediaQueriesForIPadPro129.stream() + .filter(media -> media.contains("landscape")).count()); + + // iPad Pro 11, 10.5 + Predicate iPadPro11And105 = icon -> (icon.getWidth() == 1668 + && icon.getHeight() == 2388) + || (icon.getWidth() == 2388 && icon.getHeight() == 1668); + List mediaQueriesForIPadPro11And105 = splashIconsForAppleDevices + .stream().filter(iPadPro11And105) + .map(icon -> icon.asElement().attr("media")) + .collect(Collectors.toList()); + Assert.assertEquals(1, mediaQueriesForIPadPro11And105.stream() + .filter(media -> media.contains("portrait")).count()); + Assert.assertEquals(1, mediaQueriesForIPadPro11And105.stream() + .filter(media -> media.contains("landscape")).count()); + + // iPad Air 10.5 + Predicate iPadAir105 = icon -> (icon.getWidth() == 1668 + && icon.getHeight() == 2224) + || (icon.getWidth() == 2224 && icon.getHeight() == 1668); + List mediaQueriesForIPadAir105 = splashIconsForAppleDevices + .stream().filter(iPadAir105) + .map(icon -> icon.asElement().attr("media")) + .collect(Collectors.toList()); + Assert.assertEquals(1, mediaQueriesForIPadAir105.stream() + .filter(media -> media.contains("portrait")).count()); + Assert.assertEquals(1, mediaQueriesForIPadAir105.stream() + .filter(media -> media.contains("landscape")).count()); + + // iPad 10.2 + Predicate iPad102 = icon -> (icon.getWidth() == 1620 + && icon.getHeight() == 2160) + || (icon.getWidth() == 2160 && icon.getHeight() == 1620); + List mediaQueriesForIPad102 = splashIconsForAppleDevices + .stream().filter(iPad102) + .map(icon -> icon.asElement().attr("media")) + .collect(Collectors.toList()); + Assert.assertEquals(1, mediaQueriesForIPad102.stream() + .filter(media -> media.contains("portrait")).count()); + Assert.assertEquals(1, mediaQueriesForIPad102.stream() + .filter(media -> media.contains("landscape")).count()); + + // iPad Pro 9.7, iPad Air 9.7, iPad 9.7, iPad mini 7.9 + Predicate iPad97And79 = icon -> (icon.getWidth() == 1536 + && icon.getHeight() == 2048) + || (icon.getWidth() == 2048 && icon.getHeight() == 1536); + List mediaQueriesForIPad97And79 = splashIconsForAppleDevices + .stream().filter(iPad97And79) + .map(icon -> icon.asElement().attr("media")) + .collect(Collectors.toList()); + Assert.assertEquals(1, mediaQueriesForIPad97And79.stream() + .filter(media -> media.contains("portrait")).count()); + Assert.assertEquals(1, mediaQueriesForIPad97And79.stream() + .filter(media -> media.contains("landscape")).count()); + } + + @Test + public void pwaWithCustomBaseIconPath_splashScreenIconForIPhoneDevices_includeBothOrientations() { + // iPhone 13 Pro Max, iPhone 12 Pro Max + Predicate iPhone13ProMaxAnd12ProMax = icon -> (icon + .getWidth() == 1284 && icon.getHeight() == 2778) + || (icon.getWidth() == 2778 && icon.getHeight() == 1284); + List mediaQueriesForIPhone13ProMaxAnd12ProMax = splashIconsForAppleDevices + .stream().filter(iPhone13ProMaxAnd12ProMax) + .map(icon -> icon.asElement().attr("media")) + .collect(Collectors.toList()); + Assert.assertEquals(1, mediaQueriesForIPhone13ProMaxAnd12ProMax.stream() + .filter(media -> media.contains("portrait")).count()); + Assert.assertEquals(1, mediaQueriesForIPhone13ProMaxAnd12ProMax.stream() + .filter(media -> media.contains("landscape")).count()); + + // iPhone 13 Pro, iPhone 13, iPhone 12 Pro, iPhone 12 + Predicate iPhone13ProAnd12ProAnd13And12 = icon -> (icon + .getWidth() == 1170 && icon.getHeight() == 2532) + || (icon.getWidth() == 2532 && icon.getHeight() == 1170); + List mediaQueriesForIPhone13ProAnd12ProAnd13And12 = splashIconsForAppleDevices + .stream().filter(iPhone13ProAnd12ProAnd13And12) + .map(icon -> icon.asElement().attr("media")) + .collect(Collectors.toList()); + Assert.assertEquals(1, mediaQueriesForIPhone13ProAnd12ProAnd13And12 + .stream().filter(media -> media.contains("portrait")).count()); + Assert.assertEquals(1, mediaQueriesForIPhone13ProAnd12ProAnd13And12 + .stream().filter(media -> media.contains("landscape")).count()); + + // iPhone 13 Mini, iPhone 12 Mini, iPhone 11 Pro, iPhone XS, iPhone X + Predicate iPhone13MiniAnd12MiniAnd11ProAndXSAndX = icon -> (icon + .getWidth() == 1125 && icon.getHeight() == 2436) + || (icon.getWidth() == 2436 && icon.getHeight() == 1125); + List mediaQueriesForIPhone13MiniAnd12MiniAnd11ProAndXSAndX = splashIconsForAppleDevices + .stream().filter(iPhone13MiniAnd12MiniAnd11ProAndXSAndX) + .map(icon -> icon.asElement().attr("media")) + .collect(Collectors.toList()); + Assert.assertEquals(1, + mediaQueriesForIPhone13MiniAnd12MiniAnd11ProAndXSAndX.stream() + .filter(media -> media.contains("portrait")).count()); + Assert.assertEquals(1, + mediaQueriesForIPhone13MiniAnd12MiniAnd11ProAndXSAndX.stream() + .filter(media -> media.contains("landscape")).count()); + + // iPhone 11 Pro Max, iPhone XS Max + Predicate iPhone11ProMaxAndXSMax = icon -> (icon + .getWidth() == 1242 && icon.getHeight() == 2688) + || (icon.getWidth() == 2688 && icon.getHeight() == 1242); + List mediaQueriesForIPhone11ProMaxAndXSMax = splashIconsForAppleDevices + .stream().filter(iPhone11ProMaxAndXSMax) + .map(icon -> icon.asElement().attr("media")) + .collect(Collectors.toList()); + Assert.assertEquals(1, mediaQueriesForIPhone11ProMaxAndXSMax.stream() + .filter(media -> media.contains("portrait")).count()); + Assert.assertEquals(1, mediaQueriesForIPhone11ProMaxAndXSMax.stream() + .filter(media -> media.contains("landscape")).count()); + + // iPhone 11, iPhone XR + Predicate iPhone11AndXR = icon -> (icon.getWidth() == 828 + && icon.getHeight() == 1792) + || (icon.getWidth() == 1792 && icon.getHeight() == 828); + List mediaQueriesForIPhone11AndXR = splashIconsForAppleDevices + .stream().filter(iPhone11AndXR) + .map(icon -> icon.asElement().attr("media")) + .collect(Collectors.toList()); + Assert.assertEquals(1, mediaQueriesForIPhone11AndXR.stream() + .filter(media -> media.contains("portrait")).count()); + Assert.assertEquals(1, mediaQueriesForIPhone11AndXR.stream() + .filter(media -> media.contains("landscape")).count()); + + // iPhone 8 Plus, 7 Plus, 6s Plus, 6 Plus + Predicate iPhone8PlusAnd7PlusAnd6sPlusAnd6Plus = icon -> (icon + .getWidth() == 1242 && icon.getHeight() == 2208) + || (icon.getWidth() == 2208 && icon.getHeight() == 1242); + List mediaQueriesForIPhone8PlusAnd7PlusAnd6sPlusAnd6Plus = splashIconsForAppleDevices + .stream().filter(iPhone8PlusAnd7PlusAnd6sPlusAnd6Plus) + .map(icon -> icon.asElement().attr("media")) + .collect(Collectors.toList()); + Assert.assertEquals(1, + mediaQueriesForIPhone8PlusAnd7PlusAnd6sPlusAnd6Plus.stream() + .filter(media -> media.contains("portrait")).count()); + Assert.assertEquals(1, + mediaQueriesForIPhone8PlusAnd7PlusAnd6sPlusAnd6Plus.stream() + .filter(media -> media.contains("landscape")).count()); + + // iPhone 8, 7, 6s, 6, SE 4.7 + Predicate iPhone8And7And6sAnd6AndSE47 = icon -> (icon + .getWidth() == 750 && icon.getHeight() == 1334) + || (icon.getWidth() == 1334 && icon.getHeight() == 750); + List mediaQueriesForIPhone8And7And6sAnd6AndSE47 = splashIconsForAppleDevices + .stream().filter(iPhone8And7And6sAnd6AndSE47) + .map(icon -> icon.asElement().attr("media")) + .collect(Collectors.toList()); + Assert.assertEquals(1, mediaQueriesForIPhone8And7And6sAnd6AndSE47 + .stream().filter(media -> media.contains("portrait")).count()); + Assert.assertEquals(1, mediaQueriesForIPhone8And7And6sAnd6AndSE47 + .stream().filter(media -> media.contains("landscape")).count()); + + // iPhone 5, SE 4, iPod touch 5th Gen and later + Predicate iPhone5AndSE47AndIPod5AndLater = icon -> (icon + .getWidth() == 640 && icon.getHeight() == 1136) + || (icon.getWidth() == 1136 && icon.getHeight() == 640); + List mediaQueriesForIPhone5AndSE47AndIPod5AndLater = splashIconsForAppleDevices + .stream().filter(iPhone5AndSE47AndIPod5AndLater) + .map(icon -> icon.asElement().attr("media")) + .collect(Collectors.toList()); + Assert.assertEquals(1, mediaQueriesForIPhone5AndSE47AndIPod5AndLater + .stream().filter(media -> media.contains("portrait")).count()); + Assert.assertEquals(1, mediaQueriesForIPhone5AndSE47AndIPod5AndLater + .stream().filter(media -> media.contains("landscape")).count()); + } + + private static PwaRegistry createPwaRegistryInstance(PWA pwa, ServletContext servletContext) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException { diff --git a/flow-tests/test-pwa/src/test/java/com/vaadin/flow/pwatest/ui/PwaTestIT.java b/flow-tests/test-pwa/src/test/java/com/vaadin/flow/pwatest/ui/PwaTestIT.java index 7b39ffb739f..1cde3ff57ae 100644 --- a/flow-tests/test-pwa/src/test/java/com/vaadin/flow/pwatest/ui/PwaTestIT.java +++ b/flow-tests/test-pwa/src/test/java/com/vaadin/flow/pwatest/ui/PwaTestIT.java @@ -72,7 +72,8 @@ public void testPwaResources() throws IOException, JSONException { By.xpath("//link[@rel='apple-touch-icon'][@sizes][@href]")), 1); checkIcons(head.findElements(By.xpath( - "//link[@rel='apple-touch-startup-image'][@sizes][@href]")), 4); + "//link[@rel='apple-touch-startup-image'][@sizes][@href]")), + 26); // test web manifest List elements = head