TextBox Test Plan - UQcsse3200/2026-studio-2 GitHub Wiki
Test Plan: TextBox Feature
1. Overview
This document describes the test plan for the TextBox dialogue-box feature, covering:
| Class |
Package |
Role |
TextConfig |
com.csse3200.game.entities.configs |
Plain config POJO loaded from JSON; resolves colour/alignment strings into GDX types |
TextBoxFactory |
com.csse3200.game.entities.factories |
Loads a TextConfig from disk and constructs a TextBoxComponent |
TextBoxComponent |
com.csse3200.game.components |
Renders a typewriter-effect, paged dialogue box; handles TAB input to skip/advance/dismiss |
Test implementation: JUnit 5, Mockito (mockito-junit-jupiter, mockito-inline), and the project's GameExtension headless-libGDX harness, per the "libGDX and Testing" course material.
2. Objectives
- Verify
TextConfig correctly parses and falls back on malformed/missing JSON-sourced values (colours, alignment).
- Verify
TextBoxFactory never throws regardless of config-loading outcome, and correctly delegates to FileLoader.
- Verify
TextBoxComponent's core interactive behaviour: page defaulting, the typewriter reveal timer, and the three-way TAB interaction (skip / advance page / dismiss).
- Establish a safe, headless (no GPU/display) way to exercise UI components that depend on
Gdx and ServiceLocator.
3. Scope
In scope: unit-level behaviour of the three classes above, using mocked collaborators (FileLoader, RenderService, Gdx.graphics, Gdx.input).
Out of scope:
- Visual/pixel-level correctness of the rendered nine-patch scroll background (
getBackgroundDrawable) โ not asserted beyond "doesn't throw."
- Real font rendering/metrics beyond what's needed for scene2d layout to run.
- End-to-end/manual GUI testing (see ui.vision note in course material โ libGDX can't be tested headless for true visual/functional GUI testing; out of scope for this automated suite).
- Integration with the real
flat-earth-ui.json skin asset pipeline beyond it being loadable (see Risks, ยง6).
4. Test Environment
- Runner: JUnit 5 (Jupiter), with
@ExtendWith(GameExtension.class) for any test that touches Gdx or constructs a UIComponent subclass.
- Headless backend:
GameExtension boots a HeadlessApplication and mocks Gdx.gl/Gdx.gl20 so no real GPU/display is required (safe for CI, e.g. GitHub Actions).
- Mocking: Mockito for collaborators (
FileLoader via mockito-inline static mocking, RenderService, Graphics, Input).
- State isolation:
ServiceLocator.clear() is called after each test (via GameExtension.afterEach and/or an explicit @AfterEach) to avoid state leaking between tests.
- Known environment dependency:
UIComponent.skin is a protected static final Skin loaded once from flat-earth/skin/flat-earth-ui.json via Gdx.files.internal(...) the first time the class is touched. Tests that construct a TextBoxComponent rely on that asset being resolvable from the test run's working directory; this can't be mocked away since it's a static field.
5. Test Case Summary
5.1 TextConfigTest
| # |
Test |
Verifies |
| 1 |
shouldHaveExpectedDefaultValues |
Default field values match spec (position, chars/sec, sizing, font path, alignment string, pages) |
| 2 |
defaultColoursShouldResolveCorrectly |
Default colour strings resolve to Color.BLACK / TAN / BROWN |
| 3 |
defaultAlignmentShouldResolveToCenter |
Default alignment resolves to Align.center |
| 4 |
shouldParseColourWithColorPrefix |
"Color.RED" โ Color.RED |
| 5 |
shouldParseColourWithoutColorPrefix |
"BLUE" โ Color.BLUE |
| 6 |
shouldParseColourCaseInsensitively |
"color.green" โ Color.GREEN |
| 7 |
shouldFallBackToDefaultOnUnknownColourName |
Unknown name falls back to the fallback colour |
| 8 |
shouldFallBackToDefaultOnNullColour |
null falls back to the fallback colour |
| 9 |
shouldFallBackToDefaultOnBlankColour |
Blank/whitespace falls back to the fallback colour |
| 10 |
shouldParseLeftAlignment / shouldParseRightAlignment |
"left"/"right" โ Align.left/Align.right |
| 11 |
shouldParseCentreSpellingAsCenter |
British spelling "centre" is accepted |
| 12 |
shouldParseAlignmentCaseInsensitivelyWithWhitespace |
" RIGHT " โ Align.right |
| 13 |
shouldFallBackToCenterOnUnknownAlignment / OnNullAlignment / OnBlankAlignment |
Invalid/missing alignment defaults to center |
5.2 TextBoxFactoryTest
| # |
Test |
Verifies |
| 1 |
shouldNotThrowWhenConfigFailsToLoad |
FileLoader.readClass returning null is handled without throwing (logged error path) |
| 2 |
shouldReadConfigFromGivenFilePath |
FileLoader.readClass is called with (TextConfig.class, path) exactly once |
| 3 |
shouldNotThrowWhenConfigLoadsSuccessfully |
A valid config leads to component construction/creation without an exception escaping the factory |
| 4 |
shouldNotThrowWithNullFontPath |
A config with no font path falls through to the skin's default font without throwing |
5.3 TextBoxComponentTest
| # |
Test |
Verifies |
| 1 |
shouldDefaultToSingleEmptyPageWhenPagesIsNull |
null pages โ [""] |
| 2 |
shouldDefaultToSingleEmptyPageWhenPagesIsEmpty |
Empty list โ [""] |
| 3 |
shouldKeepProvidedPagesWhenNonEmpty |
Non-empty pages are preserved as-is |
| 4 |
shouldNotBuildActorsBeforeFirstDraw |
table stays uninitialised until the first draw() call |
| 5 |
shouldGraduallyRevealCharactersOverTime |
Revealed character count increases proportionally to elapsed time ร chars/sec, capping at full length |
| 8 |
shouldNotShowTableWhenCurrentPageIsEmpty |
An empty page keeps the table hidden |
| 9 |
firstTabPressShouldInstantlyRevealRestOfPage |
TAB while still typing reveals the full page immediately, without dismissing |
| 10 |
tabOnLastFullyRevealedPageShouldDismissBox |
TAB on a fully-revealed, last page sets dismissed = true and hides the table |
| 11 |
tabOnFullyRevealedNonLastPageShouldAdvanceToNextPage |
TAB on a fully-revealed, non-last page advances currentPageIndex and resets the typing state for the new page |
| 12 |
drawShouldDoNothingOnceDismissed |
Further draw() calls after dismissal are a no-op (no exceptions, state unchanged) |
6. Risks & Assumptions
| Risk |
Mitigation / Notes |
UIComponent.skin's static initializer fails if flat-earth/skin/flat-earth-ui.json isn't resolvable in the test run's working directory |
Not mockable (static final); relies on existing project test configuration. If it fails, the error surfaces as ExceptionInInitializerError on first use, not caught by TextBoxFactory's catch (Exception e). |
Tests rely on reflection to read TextBoxComponent's private fields (pages, revealedChars, dismissed, currentPageIndex, lastSourceContent, table) |
Acceptable for a unit-test-only concern since it doesn't change production code; will need updating if those field names change. |
RenderService/ResourceService method signatures assumed from UIComponent/ServiceLocator source (now confirmed: getStage() on RenderService) |
Confirmed against actual source as of this test suite's creation. |
| No true GUI/visual testing of the scroll background artwork |
Deliberately out of scope per course guidance (headless can't validate pixels); would need a tool like ui.vision on a real display for that. |
7. Out-of-Scope / Follow-Up Suggestions
- Add an integration test that runs
TextBoxFactory.createTextBox against a real sample JSON file on the test classpath, rather than a fully mocked FileLoader.
- Add a test for
TextBoxComponent.dispose() verifying customFont.dispose() is invoked when a custom font was loaded.
- If
entity is ever set on the component, add a test covering the world-to-screen position calculation branch in draw().