fix: add a widget registry and "add widget" picker (#21)
CI / build-and-test (pull_request) Successful in 2m25s

"Widget hinzufügen" appended an empty placeholder card, so a widget closed
via its X was gone until a page reload. Widget types now live in a
WidgetRegistry (WidgetDefinition: type id, title key, default size,
content factory); the toolbar button opens a picker dialog over the
registry, and the dashboard builds its initial widgets from it too.

Chart construction moves from DashboardView into the registry factories,
which resolve translation keys through the chart component so they follow
the UI locale. Added widgets get an id of "<type>-<n>" from a
monotonically growing counter, so a closed widget's id is never handed to
a new one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0124BiJikbhbiEsNfJxdWM69
This commit is contained in:
Pit Friedrich
2026-07-28 20:32:45 +02:00
parent 03872a9d0c
commit 5fe159a604
8 changed files with 318 additions and 77 deletions
@@ -0,0 +1,48 @@
package com.example.widgets;
import com.example.data.InMemoryChartDataService;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* The definitions themselves — the factories need a Vaadin UI to resolve
* translations, so they are exercised from {@code DashboardViewTest} instead.
*/
class WidgetRegistryTest {
private final WidgetRegistry registry = new WidgetRegistry(new InMemoryChartDataService());
@Test
void definitions_areListedInRegistrationOrder() {
List<String> types = registry.definitions().stream()
.map(WidgetDefinition::type).toList();
assertEquals(List.of(WidgetRegistry.REVENUE_TREND, WidgetRegistry.REVENUE_MONTH,
WidgetRegistry.REVENUE_REGION), types);
assertTrue(registry.definitions().stream()
.allMatch(d -> d.width() > 0 && d.height() > 0),
"every definition carries a usable default size");
}
@Test
void require_rejectsAnUnknownType() {
assertThrows(IllegalArgumentException.class, () -> registry.require("nope"));
}
@Test
void register_replacesTheDefinitionOfTheSameType() {
int before = registry.definitions().size();
WidgetDefinition replacement = new WidgetDefinition(WidgetRegistry.REVENUE_TREND,
"card.revenueTrend", 2, 2, () -> null);
registry.register(replacement);
assertEquals(before, registry.definitions().size());
assertEquals(replacement, registry.require(WidgetRegistry.REVENUE_TREND));
}
}