mirror of
https://github.com/GTNewHorizons/ForgeMultipart.git
synced 2026-11-13 17:04:25 +00:00
test: characterize TileMultipart part storage and predicates
Gate step before converting the central tile. Covers the surface that does not need a world: the part list is replaced rather than mutated so a previously published Seq never changes under a caller, jPartList reflects the current parts, operate skips parts whose tile has been cleared, getLightValue is the maximum and zero when empty while getExplosionResistance is the maximum but throws when empty, occlusionTest needs agreement in both directions, canReplacePart excludes the outgoing part but still rejects a part already present, and solidity falls back to partMap being null on a bare tile. One assumption was wrong and the test caught it: TMultiPart.doesTick defaults to true, so a default part starts the tile ticking and a part has to opt out to leave it idle. The case now records the real behavior. Anything touching worldObj, the registry or packets stays out of this layer because it cannot run headless; the Forge server suite covers that side. 90 plain-JVM tests, all passing against the untouched Scala.
This commit is contained in:
1 parent
08ee01c97e
commit
beb0995e60
1 file changed
+267
@@ -0,0 +1,267 @@
|
||||
package codechicken.multipart;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import scala.collection.JavaConversions;
|
||||
import scala.collection.Seq;
|
||||
|
||||
/**
|
||||
* Covers the part of TileMultipart that does not need a world: list storage, the operate guard, and the pure predicates
|
||||
* built on top of them. Anything touching worldObj, the registry or packets is exercised by the Forge server suite
|
||||
* instead, because it cannot run headless.
|
||||
*/
|
||||
class TileMultipartCharacterizationTest {
|
||||
|
||||
@Test
|
||||
void aFreshTileHoldsNoPartsAndDoesNotTick() {
|
||||
TileMultipart tile = new TileMultipart();
|
||||
|
||||
assertEquals(0, tile.partList().size());
|
||||
assertTrue(tile.jPartList().isEmpty());
|
||||
assertFalse(tile.canUpdate());
|
||||
assertNull(tile.partMap(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void addingPartsAppendsInOrderAndBindsThem() {
|
||||
TileMultipart tile = new TileMultipart();
|
||||
CountingPart first = new CountingPart("a");
|
||||
CountingPart second = new CountingPart("b");
|
||||
|
||||
tile.addPart_do(first);
|
||||
tile.addPart_do(second);
|
||||
|
||||
assertEquals(Arrays.asList(first, second), tile.jPartList());
|
||||
assertSame(tile, first.tile());
|
||||
assertSame(tile, second.tile());
|
||||
}
|
||||
|
||||
@Test
|
||||
void thePartListIsReplacedRatherThanMutated() {
|
||||
TileMultipart tile = new TileMultipart();
|
||||
Seq<TMultiPart> before = tile.partList();
|
||||
|
||||
tile.addPart_do(new CountingPart("a"));
|
||||
|
||||
assertNotSame(before, tile.partList(), "Each mutation must publish a new immutable Seq");
|
||||
assertEquals(0, before.size(), "The previously published Seq must not change under a caller");
|
||||
assertEquals(1, tile.partList().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void jPartListReflectsTheCurrentParts() {
|
||||
TileMultipart tile = new TileMultipart();
|
||||
CountingPart part = new CountingPart("a");
|
||||
tile.addPart_do(part);
|
||||
|
||||
assertEquals(1, tile.jPartList().size());
|
||||
assertSame(part, tile.jPartList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearPartsEmptiesTheList() {
|
||||
TileMultipart tile = new TileMultipart();
|
||||
tile.addPart_do(new CountingPart("a"));
|
||||
|
||||
tile.clearParts();
|
||||
|
||||
assertEquals(0, tile.partList().size());
|
||||
assertTrue(tile.jPartList().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void operateSkipsPartsWhoseTileHasBeenCleared() {
|
||||
TileMultipart tile = new TileMultipart();
|
||||
CountingPart kept = new CountingPart("a");
|
||||
CountingPart detached = new CountingPart("b");
|
||||
tile.addPart_do(kept);
|
||||
tile.addPart_do(detached);
|
||||
detached.tile_$eq(null);
|
||||
|
||||
tile.onChunkLoad();
|
||||
|
||||
assertEquals(1, kept.chunkLoads);
|
||||
assertEquals(0, detached.chunkLoads, "A part with a null tile must be skipped");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ticksOnlyOnceATickingPartIsPresent() {
|
||||
TileMultipart quiet = new TileMultipart();
|
||||
assertFalse(quiet.canUpdate());
|
||||
|
||||
// TMultiPart.doesTick defaults to true, so a part must opt out to leave the tile idle.
|
||||
quiet.addPart_do(new StillPart());
|
||||
assertFalse(quiet.canUpdate());
|
||||
|
||||
TileMultipart ticking = new TileMultipart();
|
||||
ticking.addPart_do(new CountingPart("a"));
|
||||
assertTrue(ticking.canUpdate(), "A default part ticks, so it starts the tile ticking");
|
||||
}
|
||||
|
||||
@Test
|
||||
void lightValueIsTheMaximumAndZeroWhenEmpty() {
|
||||
TileMultipart tile = new TileMultipart();
|
||||
assertEquals(0, tile.getLightValue());
|
||||
|
||||
tile.addPart_do(new LightPart(4));
|
||||
tile.addPart_do(new LightPart(11));
|
||||
tile.addPart_do(new LightPart(7));
|
||||
|
||||
assertEquals(11, tile.getLightValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void explosionResistanceIsTheMaximumButThrowsWhenEmpty() {
|
||||
TileMultipart tile = new TileMultipart();
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () -> tile.getExplosionResistance(null));
|
||||
|
||||
tile.addPart_do(new ResistancePart(2f));
|
||||
tile.addPart_do(new ResistancePart(9f));
|
||||
|
||||
assertEquals(9f, tile.getExplosionResistance(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void occlusionTestRequiresAgreementInBothDirections() {
|
||||
TileMultipart tile = new TileMultipart();
|
||||
TMultiPart accepting = new CountingPart("a");
|
||||
RejectingPart rejecting = new RejectingPart();
|
||||
|
||||
assertTrue(tile.occlusionTest(seq(accepting), new CountingPart("b")));
|
||||
assertFalse(tile.occlusionTest(seq(rejecting), new CountingPart("b")), "An existing part may refuse");
|
||||
assertFalse(tile.occlusionTest(seq(accepting), rejecting), "The incoming part may refuse");
|
||||
assertTrue(tile.occlusionTest(seq(), rejecting), "Nothing to conflict with");
|
||||
}
|
||||
|
||||
@Test
|
||||
void canReplacePartIgnoresTheOutgoingPartAndRejectsDuplicates() {
|
||||
TileMultipart tile = new TileMultipart();
|
||||
RejectingPart outgoing = new RejectingPart();
|
||||
CountingPart other = new CountingPart("b");
|
||||
tile.addPart_do(outgoing);
|
||||
tile.addPart_do(other);
|
||||
|
||||
// The outgoing part is excluded from the test even though it refuses everything.
|
||||
assertTrue(tile.canReplacePart(outgoing, new CountingPart("c")));
|
||||
// A part already present, other than the outgoing one, cannot be added again.
|
||||
assertFalse(tile.canReplacePart(outgoing, other));
|
||||
}
|
||||
|
||||
@Test
|
||||
void solidityAndTorchPlacementFallBackToTheParts() {
|
||||
TileMultipart tile = new TileMultipart();
|
||||
|
||||
// partMap is null on a bare tile, so nothing is solid and no torch may be placed.
|
||||
assertFalse(tile.isSolid(1));
|
||||
assertFalse(tile.canPlaceTorchOnTop());
|
||||
|
||||
tile.addPart_do(new TorchSupportingPart());
|
||||
assertTrue(tile.canPlaceTorchOnTop());
|
||||
}
|
||||
|
||||
private static Seq<TMultiPart> seq(TMultiPart... parts) {
|
||||
List<TMultiPart> list = new ArrayList<>(Arrays.asList(parts));
|
||||
return JavaConversions.asScalaBuffer(list).toList();
|
||||
}
|
||||
|
||||
private static class CountingPart extends TMultiPart {
|
||||
|
||||
private final String type;
|
||||
private int chunkLoads;
|
||||
|
||||
private CountingPart(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkLoad() {
|
||||
chunkLoads++;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StillPart extends CountingPart {
|
||||
|
||||
private StillPart() {
|
||||
super("still");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doesTick() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class LightPart extends CountingPart {
|
||||
|
||||
private final int light;
|
||||
|
||||
private LightPart(int light) {
|
||||
super("light");
|
||||
this.light = light;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLightValue() {
|
||||
return light;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ResistancePart extends CountingPart {
|
||||
|
||||
private final float resistance;
|
||||
|
||||
private ResistancePart(float resistance) {
|
||||
super("resistance");
|
||||
this.resistance = resistance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float explosionResistance(Entity entity) {
|
||||
return resistance;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RejectingPart extends CountingPart {
|
||||
|
||||
private RejectingPart() {
|
||||
super("rejecting");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean occlusionTest(TMultiPart npart) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TorchSupportingPart extends CountingPart {
|
||||
|
||||
private TorchSupportingPart() {
|
||||
super("torchsupport");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPlaceTorchOnTop() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user