Repository navigation
Rearranging Deck Testing
Tests were designed against each method's control flow graph (CFG): for
every if/loop condition in the methods under test, at least one test was written to exercise each
outcome (true and false, or "found" and "not found"). Coverage numbers below are JaCoCo branch
coverage, gathered from a fresh ./gradlew :core:test :core:jacocoTestReport run. Branch coverage
is the relevant metric here since the logic in both features is almost entirely decision-driven
(guards, membership checks, loop termination) rather than long straight-line computation.
flowchart TD
A["rearrangeHand(newHand)"] --> B{"newHand == null?"}
B -- yes --> C["throw IllegalArgumentException"]
B -- no --> D["filter out on-cooldown instances"]
D --> E{"playableSelection == current hand?"}
E -- yes --> F["return false, no-op"]
E -- no --> G{"canAfford && spendEnergy?"}
G -- no --> H["throw IllegalStateException"]
G -- yes --> I["battleDeck.setHandInstances(playableSelection)"]
I --> J{"threw?"}
J -- yes --> K["restoreEnergy, rethrow"]
J -- no --> L["return true"]
M["setHandInstances(newHand)"] --> N{"newHand == null?"}
N -- yes --> O["throw IllegalArgumentException"]
N -- no --> P["for each instance in newHand"]
P --> Q{"instance available in hand/drawPile?"}
Q -- no --> R["throw IllegalArgumentException"]
Q -- yes --> S["move to resolved hand"]
S --> P
P -- done --> T["commit: hand = resolved, rest reshuffled into drawPile"]
| Branch | Covered by | Outcome tested |
|---|---|---|
| filter: instance on cooldown | shouldAllowSelectingAnInstanceOnCooldownWithoutMovingItOrItsSiblingCard |
discarded pick is excluded from the physical hand, left untouched |
| filter: instance not on cooldown | shouldRearrangeHandToExactSelectedInstancesAndChargeOneEnergy |
playable pick moves into the hand |
playableSelection == current hand → true (no-op) |
shouldNotChargeEnergyWhenRearrangedHandMatchesCurrentHand |
returns false, no energy spent |
| → false (genuine change) | shouldRearrangeHandToExactSelectedInstancesAndChargeOneEnergy |
returns true, 1 energy spent |
canAfford && spendEnergy → false (can't afford) |
(not tested) | no test constructs a low/zero-energy EnergyComponent and attempts a genuine rearrange, this path (and the IllegalStateException) is unverified |
setHandInstances throws → rollback |
(not tested) | no test forces setHandInstances to throw mid-rearrangeHand to verify restoreEnergy actually runs; the rollback branch exists in code but isn't exercised |
newHand == null (both methods) |
(not tested) | defensive guards, not exercised by name |
instance not available (setHandInstances) |
shouldRejectSettingHandToAnInstanceOnCooldownInDiscard, shouldRejectSettingHandToAnUnknownInstance
|
rejects a discarded instance and a wholly foreign instance respectively |
| instance available |
shouldSetHandToExactChosenInstances, shouldLeaveDiscardPileUntouchedWhenSettingHand
|
accepts and commits the selection |
Measured coverage: BattleDeck 86/99 lines (87%), 38/44 branches (86%), 22/25 methods (88%);
CardPlayService 101/114 lines (89%), 60/88 branches (68%) across the whole class (the
rearrange-specific branches are more fully covered than that number suggests, it's diluted by the
unrelated card-play code in the same class).