fix(ui): fit quest board dialogs on screen
Release / release (push) Successful in 2m23s
CI / build (push) Successful in 1m7s

This commit is contained in:
dmg
2026-09-05 08:52:28 -04:00
parent 719cd49609
commit 2624c2bc02
8 changed files with 528 additions and 92 deletions
@@ -1,8 +1,11 @@
package games.dmg.spigotquestboard;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.time.Clock;
@@ -16,6 +19,144 @@ import org.junit.jupiter.api.Test;
final class QuestBoardDialogUiTest {
private static final Instant NOW = Instant.parse("2026-09-05T03:00:00Z");
@Test
void dashboardUsesCompactDedicatedNavigableScreens() {
RecordingCreator creator = new RecordingCreator(false);
QuestBoardDialogUi ui = new QuestBoardDialogUi(
creator, now -> List.of(creator.quest(NOW)), Clock.fixed(NOW, ZoneOffset.UTC)
);
QuestBoardDialogSpec specification = ui.specification(mock(Player.class));
assertEquals(420, specification.maximumBodyWidth());
assertEquals(List.of("Browse quests", "Create quest", "Pending claims"),
specification.dashboardActions());
assertEquals(1, specification.questEntries().size());
org.junit.jupiter.api.Assertions.assertTrue(
specification.questEntries().getFirst().hasBackAction()
);
}
@Test
void dedicatedScreensKeepContentSeparateAndProvideBackNavigation() {
RecordingCreator creator = new RecordingCreator(false);
QuestBoardDialogUi ui = new QuestBoardDialogUi(
creator, now -> List.of(creator.quest(NOW)), null, null, player ->
new ClaimCollectionResult(0, 0), Clock.fixed(NOW, ZoneOffset.UTC)
);
QuestBoardDialogSpec specification = ui.specification(mock(Player.class));
assertEquals("Choose what you want to do.", specification.dashboard().message());
assertTrue(specification.browse().message().contains("Select a quest"));
assertTrue(specification.create().message().contains("reward in your main hand"));
assertEquals("No pending claims.", specification.claims().message());
assertFalse(specification.dashboard().hasBackAction());
assertTrue(specification.browse().hasBackAction());
assertTrue(specification.create().hasBackAction());
assertTrue(specification.claims().hasBackAction());
assertTrue(specification.questEntries().stream().allMatch(
QuestBoardDialogSpec.QuestEntry::hasBackAction
));
}
@Test
void questDetailsShowRequiredContentAndRouteAvailableActions() {
RecordingCreator creator = new RecordingCreator(false);
Quest quest = creator.quest(NOW);
Player issuer = mock(Player.class);
when(issuer.getUniqueId()).thenReturn(quest.issuerId());
QuestBoardDialogUi ui = new QuestBoardDialogUi(
creator, now -> List.of(quest), new RecordingCompleter(), new RecordingCanceller(),
null, Clock.fixed(NOW, ZoneOffset.UTC)
);
QuestBoardDialogSpec specification = ui.specification(issuer);
QuestBoardDialogSpec.QuestEntry entry = specification.questEntries().getFirst();
assertTrue(entry.message().contains("64 × STONE"));
assertTrue(entry.message().contains("Reward: 1 × DIAMOND"));
assertTrue(entry.message().contains("Issuer: Issuer"));
assertTrue(entry.message().contains("Time remaining: 7d"));
assertEquals(
List.of(QuestBoardDialogSpec.Route.COMPLETE, QuestBoardDialogSpec.Route.CANCEL),
entry.actions().stream().map(QuestBoardDialogSpec.Action::route).toList()
);
assertTrue(entry.actions().stream().allMatch(
action -> quest.id().toString().equals(action.questId())
));
}
@Test
void dashboardCreationAndClaimsDeclareTheirActionRoutesAndInputs() {
RecordingCreator creator = new RecordingCreator(false);
QuestBoardDialogUi ui = new QuestBoardDialogUi(
creator, now -> List.of(), null, null, player ->
new ClaimCollectionResult(0, 0), Clock.fixed(NOW, ZoneOffset.UTC)
);
QuestBoardDialogSpec specification = ui.specification(mock(Player.class));
assertEquals(
List.of(
QuestBoardDialogSpec.Route.OPEN_BROWSE,
QuestBoardDialogSpec.Route.OPEN_CREATE,
QuestBoardDialogSpec.Route.OPEN_CLAIMS
),
specification.dashboard().actions().stream()
.map(QuestBoardDialogSpec.Action::route)
.toList()
);
assertEquals(List.of("requested_material", "requested_quantity"),
specification.create().inputs().stream()
.map(QuestBoardDialogSpec.Input::key)
.toList());
assertEquals(QuestBoardDialogSpec.Route.SUBMIT_CREATE,
specification.create().actions().getFirst().route());
assertEquals(QuestBoardDialogSpec.Route.COLLECT,
specification.claims().actions().getFirst().route());
}
@Test
void largeQuestCollectionsRemainIndividualNavigableEntries() {
RecordingCreator creator = new RecordingCreator(false);
List<Quest> quests = java.util.stream.IntStream.range(0, 250)
.mapToObj(index -> new Quest(
new UUID(0, index + 100L), new UUID(0, index + 1L), "Issuer " + index,
"STONE", index + 1, List.of(new EscrowItem("DIAMOND", 1, null)),
NOW, NOW.plusSeconds(604800)
))
.toList();
QuestBoardDialogUi ui = new QuestBoardDialogUi(
creator, now -> quests, Clock.fixed(NOW, ZoneOffset.UTC)
);
QuestBoardDialogSpec specification = ui.specification(mock(Player.class));
assertEquals(250, specification.questEntries().size());
assertTrue(specification.questEntries().stream().allMatch(
QuestBoardDialogSpec.QuestEntry::hasBackAction
));
assertFalse(specification.browse().message().contains(quests.getLast().id().toString()));
}
@Test
void nonIssuersCannotReceiveCancellationRoutes() {
RecordingCreator creator = new RecordingCreator(false);
Player player = mock(Player.class);
when(player.getUniqueId()).thenReturn(UUID.randomUUID());
QuestBoardDialogUi ui = new QuestBoardDialogUi(
creator, now -> List.of(creator.quest(NOW)), new RecordingCompleter(),
new RecordingCanceller(), Clock.fixed(NOW, ZoneOffset.UTC)
);
QuestBoardDialogSpec.QuestEntry entry = ui.specification(player)
.questEntries().getFirst();
assertEquals(List.of(QuestBoardDialogSpec.Route.COMPLETE),
entry.actions().stream().map(QuestBoardDialogSpec.Action::route).toList());
}
@Test
void disabledPlayerCommandsDoNotGateBoardUiGateways() throws Exception {
PlayerCommandSettings settings = new PlayerCommandSettings(