package com.example.views; import com.example.Application; import com.example.components.BarChart; import com.example.components.GridStackItem; import com.example.components.GridStackLayout; import com.example.components.KpiTile; import com.example.components.LineChart; import com.example.components.PieChart; import com.vaadin.browserless.SpringBrowserlessTest; import com.vaadin.browserless.ViewPackages; import com.vaadin.browserless.internal.ElementUtilsKt; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.dom.DomEvent; import com.vaadin.flow.internal.JacksonUtils; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @SpringBootTest(classes = Application.class) @ViewPackages(classes = DashboardView.class) class DashboardViewTest extends SpringBrowserlessTest { /** Four KPI tiles plus three charts and the hint card. */ private static final int DEFAULT_WIDGETS = 8; @Test void view_rendersGridWithDefaultWidgets() { navigate(DashboardView.class); GridStackLayout grid = $view(GridStackLayout.class).first(); assertNotNull(grid); List layout = grid.getLayout(); assertEquals(DEFAULT_WIDGETS, layout.size(), "expected the default widgets"); assertTrue(layout.stream().allMatch(p -> p.w() > 0 && p.h() > 0)); } @Test void kpiTiles_renderValueAndDelta() { navigate(DashboardView.class); List tiles = $view(KpiTile.class).all(); assertEquals(4, tiles.size(), "expected the four KPI tiles"); KpiTile revenue = tiles.getFirst(); assertEquals(translate("kpi.revenueTotal"), revenue.getLabel()); assertEquals(translate("kpi.revenueTotal.value"), revenue.getValue()); // +12.4 — sign is explicit, decimal separator is the locale's. assertTrue(revenue.getDeltaText().matches("\\+12[.,]4 %"), "unexpected delta text: " + revenue.getDeltaText()); KpiTile orders = tiles.get(1); assertTrue(orders.getDeltaText().startsWith("-"), "a negative delta keeps its minus sign: " + orders.getDeltaText()); assertNotNull(orders.getSparkline(), "each tile carries a sparkline"); } @Test void kpiTiles_areOneRowHigh() { navigate(DashboardView.class); List kpis = $view(GridStackLayout.class).first().getLayout() .stream().filter(p -> p.id().startsWith("kpi-")).toList(); assertEquals(4, kpis.size()); assertTrue(kpis.stream().allMatch(p -> p.w() == 3 && p.h() == 1), "KPI tiles default to a quarter row, one cell high"); } @Test void view_rendersTheThreeCharts() { navigate(DashboardView.class); assertNotNull($view(LineChart.class).first()); assertNotNull($view(BarChart.class).first()); assertNotNull($view(PieChart.class).first()); } @Test void addAndCloseWidget_changesItemCount() { navigate(DashboardView.class); GridStackLayout grid = $view(GridStackLayout.class).first(); int initial = grid.getLayout().size(); button("gridstack.addWidget").click(); assertEquals(initial + 1, grid.getLayout().size()); clickCloseButton($view(GridStackItem.class).last()); assertEquals(initial, grid.getLayout().size()); } @Test void closeButton_removesTheClickedWidgetOnly() { navigate(DashboardView.class); GridStackLayout grid = $view(GridStackLayout.class).first(); GridStackItem first = $view(GridStackItem.class).first(); String closedId = first.getItemId(); clickCloseButton(first); List layout = grid.getLayout(); assertEquals(DEFAULT_WIDGETS - 1, layout.size()); assertFalse(layout.stream().anyMatch(p -> closedId.equals(p.id())), "the closed widget must be gone from the layout"); } @Test void closeListener_firesOnceItemIsDetached() { navigate(DashboardView.class); GridStackLayout grid = $view(GridStackLayout.class).first(); GridStackItem item = $view(GridStackItem.class).first(); int[] fired = {0}; item.addCloseListener(e -> fired[0]++); item.close(); assertEquals(1, fired[0]); assertEquals(DEFAULT_WIDGETS - 1, grid.getLayout().size()); } @Test void nonClosableItem_hasNoCloseButton() { navigate(DashboardView.class); GridStackItem item = $view(GridStackItem.class).first(); assertTrue(item.isClosable(), "items are closable by default"); item.setClosable(false); assertFalse(item.isClosable()); assertFalse(item.getChildren().anyMatch(child -> child == item.getCloseButton())); } /** Fires the DOM click the close button listens for, rather than calling * {@code close()} directly, so the button's own wiring is covered too. */ private void clickCloseButton(GridStackItem item) { Div closeButton = item.getCloseButton(); ElementUtilsKt._fireDomEvent(closeButton.getElement(), new DomEvent(closeButton.getElement(), "click", JacksonUtils.createObjectNode())); } private Button button(String translationKey) { return $view(Button.class).withText(translate(translationKey)).first(); } private String translate(String translationKey) { return getCurrentView().getElement().getComponent() .orElseThrow().getTranslation(translationKey); } }