|
18 | 18 | import java.io.File; |
19 | 19 | import java.nio.charset.StandardCharsets; |
20 | 20 | import java.nio.file.Files; |
| 21 | +import java.util.Arrays; |
| 22 | +import java.util.HashSet; |
21 | 23 | import java.util.List; |
22 | 24 | import java.util.Optional; |
23 | 25 | import java.util.Set; |
|
30 | 32 | import org.mockito.MockedStatic; |
31 | 33 | import org.mockito.Mockito; |
32 | 34 |
|
| 35 | +import com.vaadin.flow.di.Lookup; |
33 | 36 | import com.vaadin.flow.internal.ActiveStyleSheetTracker; |
34 | 37 | import com.vaadin.flow.internal.BrowserLiveReload; |
35 | 38 | import com.vaadin.flow.internal.BrowserLiveReloadAccessor; |
36 | 39 | import com.vaadin.flow.server.VaadinContext; |
37 | 40 | import com.vaadin.flow.server.startup.ApplicationConfiguration; |
38 | 41 |
|
39 | 42 | import static org.junit.Assert.assertTrue; |
| 43 | +import static org.mockito.ArgumentMatchers.anyString; |
40 | 44 | import static org.mockito.ArgumentMatchers.argThat; |
41 | 45 | import static org.mockito.ArgumentMatchers.eq; |
42 | 46 |
|
@@ -293,4 +297,101 @@ public void cssChange_ignoresVaadinThemeUrls_noLiveReloadUpdates() |
293 | 297 | }); |
294 | 298 | } |
295 | 299 | } |
| 300 | + |
| 301 | + @Test |
| 302 | + public void vaadinThemeUrls_areNotBundled_whenCssChangesDetected() |
| 303 | + throws Exception { |
| 304 | + // Prepare a temp directory to watch |
| 305 | + File root = temporaryFolder.newFolder("watched-root"); |
| 306 | + |
| 307 | + // Mocks for Vaadin context and configuration (dev mode) |
| 308 | + VaadinContext ctx = Mockito.mock(VaadinContext.class); |
| 309 | + ApplicationConfiguration config = Mockito |
| 310 | + .mock(ApplicationConfiguration.class); |
| 311 | + Mockito.when(config.isProductionMode()).thenReturn(false); |
| 312 | + |
| 313 | + // Mock BrowserLiveReload plumbing via Lookup |
| 314 | + Lookup lookup = Mockito.mock(Lookup.class); |
| 315 | + BrowserLiveReload liveReload = Mockito.mock(BrowserLiveReload.class); |
| 316 | + BrowserLiveReloadAccessor accessor = Mockito |
| 317 | + .mock(BrowserLiveReloadAccessor.class); |
| 318 | + Mockito.when(accessor.getLiveReload(ctx)).thenReturn(liveReload); |
| 319 | + Mockito.when(lookup.lookup(BrowserLiveReloadAccessor.class)) |
| 320 | + .thenReturn(accessor); |
| 321 | + Mockito.when(ctx.getAttribute(eq(Lookup.class))).thenReturn(lookup); |
| 322 | + |
| 323 | + // Mock Static: ApplicationConfiguration.get(context) |
| 324 | + try (MockedStatic<ApplicationConfiguration> appConfig = Mockito |
| 325 | + .mockStatic(ApplicationConfiguration.class); |
| 326 | + // Mock Static: |
| 327 | + // PublicStyleSheetBundler.forResourceLocations(...) |
| 328 | + MockedStatic<PublicStyleSheetBundler> bundlerStatic = Mockito |
| 329 | + .mockStatic(PublicStyleSheetBundler.class)) { |
| 330 | + |
| 331 | + appConfig.when(() -> ApplicationConfiguration.get(Mockito.any())) |
| 332 | + .thenReturn(config); |
| 333 | + |
| 334 | + // Active URLs include two Lumo URLs and one regular URL |
| 335 | + Set<String> activeUrls = new HashSet<>(Arrays.asList( |
| 336 | + "/lumo/utility.css", "lumo/utility.css", |
| 337 | + "/lumo/presets/compact.css", "lumo/presets/compact.css", |
| 338 | + "/aura/aura.css", "aura/aura.css", "/css/app.css", |
| 339 | + "context://css/app.css", "base://css/app.css", |
| 340 | + "http://localhost:8080/hello")); |
| 341 | + ActiveStyleSheetTracker tracker = Mockito |
| 342 | + .mock(ActiveStyleSheetTracker.class); |
| 343 | + Mockito.when(tracker.getActiveUrls()).thenReturn(activeUrls); |
| 344 | + // Ensure context returns our tracker for attribute-based access |
| 345 | + Mockito.when(ctx.getAttribute( |
| 346 | + Mockito.eq(ActiveStyleSheetTracker.class), Mockito.any())) |
| 347 | + .thenReturn(tracker); |
| 348 | + |
| 349 | + // Bundler mock |
| 350 | + PublicStyleSheetBundler bundler = Mockito |
| 351 | + .mock(PublicStyleSheetBundler.class); |
| 352 | + // Return some Optional content for non-Lumo URLs to allow |
| 353 | + // liveReload.update |
| 354 | + Mockito.when(bundler.bundle(anyString(), anyString())) |
| 355 | + .thenReturn(Optional.of("css")); |
| 356 | + bundlerStatic |
| 357 | + .when(() -> PublicStyleSheetBundler |
| 358 | + .forResourceLocations(Mockito.anyList())) |
| 359 | + .thenReturn(bundler); |
| 360 | + |
| 361 | + // Start the updater watching the temp root |
| 362 | + try (PublicResourcesLiveUpdater ignored = new PublicResourcesLiveUpdater( |
| 363 | + List.of(root.getAbsolutePath()), ctx)) { |
| 364 | + // Trigger a CSS change under the watched root |
| 365 | + File changed = new File(root, "trigger.css"); |
| 366 | + Files.writeString(changed.toPath(), "body{}\n"); |
| 367 | + |
| 368 | + // Wait until the non-Lumo URL is processed (bundled at least |
| 369 | + // once) |
| 370 | + Awaitility.await().untilAsserted(() -> { |
| 371 | + Mockito.verify(bundler, Mockito.times(1)) |
| 372 | + .bundle(eq("/css/app.css"), anyString()); |
| 373 | + Mockito.verify(bundler, Mockito.times(1)) |
| 374 | + .bundle(eq("context://css/app.css"), anyString()); |
| 375 | + Mockito.verify(bundler, Mockito.times(1)) |
| 376 | + .bundle(eq("base://css/app.css"), anyString()); |
| 377 | + }); |
| 378 | + |
| 379 | + // Verify bundler.bundle was NEVER called for Lumo URLs |
| 380 | + Mockito.verify(bundler, Mockito.never()) |
| 381 | + .bundle(eq("/lumo/utility.css"), anyString()); |
| 382 | + Mockito.verify(bundler, Mockito.never()) |
| 383 | + .bundle(eq("lumo/utility.css"), anyString()); |
| 384 | + Mockito.verify(bundler, Mockito.never()) |
| 385 | + .bundle(eq("/lumo/presets/compact.css"), anyString()); |
| 386 | + Mockito.verify(bundler, Mockito.never()) |
| 387 | + .bundle(eq("lumo/presets/compact.css"), anyString()); |
| 388 | + Mockito.verify(bundler, Mockito.never()) |
| 389 | + .bundle(eq("/aura/aura.css"), anyString()); |
| 390 | + Mockito.verify(bundler, Mockito.never()) |
| 391 | + .bundle(eq("aura/aura.css"), anyString()); |
| 392 | + Mockito.verify(bundler, Mockito.never()) |
| 393 | + .bundle(eq("http://localhost:8080/hello"), anyString()); |
| 394 | + } |
| 395 | + } |
| 396 | + } |
296 | 397 | } |
0 commit comments