diff --git a/src/test/java/net/rptools/maptool/model/PathTest.java b/src/test/java/net/rptools/maptool/model/PathTest.java new file mode 100644 index 0000000000..457825e33a --- /dev/null +++ b/src/test/java/net/rptools/maptool/model/PathTest.java @@ -0,0 +1,681 @@ +/* + * This software Copyright by the RPTools.net development team, and + * licensed under the Affero GPL Version 3 or, at your option, any later + * version. + * + * MapTool Source Code is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU Affero General Public + * License * along with this source Code. If not, please visit + * and specifically the Affero license + * text at . + */ +package net.rptools.maptool.model; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class PathTest { + @Test + @DisplayName("Test that a new path is empty") + public void testNewPath() { + var path = new Path<>(); + + assertTrue(path.getCellPath().isEmpty()); + assertTrue(path.getWayPointList().isEmpty()); + } + + @Test + @DisplayName( + "Test that appending a waypoint to an empty path results in a path with one cell and one waypoint") + public void testSingletonPath() { + var path = new Path(); + var point = new ZonePoint(1, 3); + + path.appendWaypoint(point); + + assertIterableEquals(List.of(point), path.getCellPath()); + assertIterableEquals(List.of(point), path.getWayPointList()); + } + + @Test + @DisplayName( + "Test that appending a partial path to an empty path sets the first and last point as waypoints") + public void testAddingPartialPathToEmptyPath() { + var path = new Path(); + var partialPath = + List.of(new ZonePoint(1, 3), new ZonePoint(2, 2), new ZonePoint(3, 1), new ZonePoint(4, 0)); + + path.appendPartialPath(partialPath); + + assertIterableEquals(partialPath, path.getCellPath()); + var expectedWaypoints = List.of(new ZonePoint(1, 3), new ZonePoint(4, 0)); + assertIterableEquals(expectedWaypoints, path.getWayPointList()); + } + + @Test + @DisplayName( + "Test that appending an singleton partial path to an empty path adds the single point as the only cell and waypoint in the result") + public void testAddingSingletonPartialPathToEmptyPath() { + var path = new Path(); + var partialPath = List.of(new ZonePoint(1, 3)); + + path.appendPartialPath(partialPath); + + assertIterableEquals(partialPath, path.getCellPath()); + assertIterableEquals(partialPath, path.getWayPointList()); + } + + @Test + @DisplayName( + "Test that appending a partial path to a singleton path extends the path and adds only the last point as a waypoint") + public void testAddingPartialPathToSingletonPath() { + var path = new Path(); + path.appendWaypoint(new ZonePoint(0, 4)); + + path.appendPartialPath( + List.of( + new ZonePoint(1, 3), new ZonePoint(2, 2), new ZonePoint(3, 1), new ZonePoint(4, 0))); + + var expectedCellPoints = + List.of( + new ZonePoint(0, 4), + new ZonePoint(1, 3), + new ZonePoint(2, 2), + new ZonePoint(3, 1), + new ZonePoint(4, 0)); + assertIterableEquals(expectedCellPoints, path.getCellPath()); + var expectedWaypoints = List.of(new ZonePoint(0, 4), new ZonePoint(4, 0)); + assertIterableEquals(expectedWaypoints, path.getWayPointList()); + } + + @Test + @DisplayName( + "Test that appending a partial path to a non-empty path extends the path and adds only the last point as a waypoint") + public void testAddingPartialPathToPath() { + var path = new Path(); + path.appendWaypoint(new ZonePoint(0, 4)); + path.appendPartialPath( + List.of( + new ZonePoint(1, 3), new ZonePoint(2, 2), new ZonePoint(3, 1), new ZonePoint(4, 0))); + + path.appendPartialPath( + List.of(new ZonePoint(5, -1), new ZonePoint(6, -2), new ZonePoint(7, -3))); + + var expectedCellPoints = + List.of( + new ZonePoint(0, 4), + new ZonePoint(1, 3), + new ZonePoint(2, 2), + new ZonePoint(3, 1), + new ZonePoint(4, 0), + new ZonePoint(5, -1), + new ZonePoint(6, -2), + new ZonePoint(7, -3)); + assertIterableEquals(expectedCellPoints, path.getCellPath()); + var expectedWaypoints = List.of(new ZonePoint(0, 4), new ZonePoint(4, 0), new ZonePoint(7, -3)); + assertIterableEquals(expectedWaypoints, path.getWayPointList()); + } + + @Test + @DisplayName( + "Test that appending an empty partial path to a non-empty path does not modify the path") + public void testAddingEmptyPartialPathToPath() { + var path = new Path(); + path.appendWaypoint(new ZonePoint(0, 4)); + path.appendPartialPath( + List.of( + new ZonePoint(1, 3), new ZonePoint(2, 2), new ZonePoint(3, 1), new ZonePoint(4, 0))); + + path.appendPartialPath(List.of()); + + var expectedCellPoints = + List.of( + new ZonePoint(0, 4), + new ZonePoint(1, 3), + new ZonePoint(2, 2), + new ZonePoint(3, 1), + new ZonePoint(4, 0)); + assertIterableEquals(expectedCellPoints, path.getCellPath()); + var expectedWaypoints = List.of(new ZonePoint(0, 4), new ZonePoint(4, 0)); + assertIterableEquals(expectedWaypoints, path.getWayPointList()); + } + + @Test + @DisplayName( + "Test that appending an singleton partial path to a non-empty path adds the single point as a cell and as a waypoint") + public void testAddingSingletonPartialPathToPath() { + var path = new Path(); + path.appendWaypoint(new ZonePoint(0, 4)); + path.appendPartialPath( + List.of( + new ZonePoint(1, 3), new ZonePoint(2, 2), new ZonePoint(3, 1), new ZonePoint(4, 0))); + + path.appendPartialPath(List.of(new ZonePoint(5, -1))); + + var expectedCellPoints = + List.of( + new ZonePoint(0, 4), + new ZonePoint(1, 3), + new ZonePoint(2, 2), + new ZonePoint(3, 1), + new ZonePoint(4, 0), + new ZonePoint(5, -1)); + assertIterableEquals(expectedCellPoints, path.getCellPath()); + var expectedWaypoints = List.of(new ZonePoint(0, 4), new ZonePoint(4, 0), new ZonePoint(5, -1)); + assertIterableEquals(expectedWaypoints, path.getWayPointList()); + } + + @Test + @DisplayName("Test that copying a path returns an equivalent path with unique point objects") + public void testCopyPath() { + var path = new Path(); + path.appendWaypoint(new ZonePoint(0, 4)); + path.appendPartialPath( + List.of( + new ZonePoint(1, 3), new ZonePoint(2, 2), new ZonePoint(3, 1), new ZonePoint(4, 0))); + + var copy = path.copy(); + + var expectedCellPoints = + List.of( + new ZonePoint(0, 4), + new ZonePoint(1, 3), + new ZonePoint(2, 2), + new ZonePoint(3, 1), + new ZonePoint(4, 0)); + assertIterableEquals(expectedCellPoints, copy.getCellPath()); + var expectedWaypoints = List.of(new ZonePoint(0, 4), new ZonePoint(4, 0)); + assertIterableEquals(expectedWaypoints, copy.getWayPointList()); + + for (int i = 0; i < path.getCellPath().size(); ++i) { + var originalPoint = path.getCellPath().get(i); + var copyPoint = copy.getCellPath().get(i); + assertNotSame(originalPoint, copyPoint); + } + for (int i = 0; i < path.getWayPointList().size(); ++i) { + var originalPoint = path.getWayPointList().get(i); + var copyPoint = copy.getWayPointList().get(i); + assertNotSame(originalPoint, copyPoint); + } + } + + @Test + @DisplayName("Test that isWaypoint() agrees with getWaypointList()") + public void testIsWaypoint() { + var path = new Path(); + path.appendWaypoint(new ZonePoint(0, 4)); + path.appendPartialPath( + List.of( + new ZonePoint(1, 3), new ZonePoint(2, 2), new ZonePoint(3, 1), new ZonePoint(4, 0))); + path.appendPartialPath( + List.of(new ZonePoint(5, -1), new ZonePoint(6, -2), new ZonePoint(7, -3))); + + var expectedWaypoints = List.of(new ZonePoint(0, 4), new ZonePoint(4, 0), new ZonePoint(7, -3)); + for (var waypoint : expectedWaypoints) { + assertTrue( + path.isWaypoint(waypoint), + () -> String.format("Point %s should be a waypoint", waypoint)); + } + var expectedNotWaypoints = + List.of( + new ZonePoint(1, 3), + new ZonePoint(2, 2), + new ZonePoint(3, 1), + new ZonePoint(5, -1), + new ZonePoint(6, -2)); + for (var notWaypoint : expectedNotWaypoints) { + assertFalse( + path.isWaypoint(notWaypoint), + () -> String.format("Point %s should not be a waypoint", notWaypoint)); + } + } + + @Test + @DisplayName("Test that an empty path can be converted to a DTO and back") + public void testEmptyDto() { + var path = new Path(); + + var dto = path.toDto(); + var newPath = Path.fromDto(dto); + + assertTrue(newPath.getCellPath().isEmpty()); + assertTrue(newPath.getWayPointList().isEmpty()); + } + + @Test + @DisplayName("Test that a path of ZonePoint can be converted to and from a DTO") + public void testDtoZonePoint() { + var path = new Path(); + path.appendWaypoint(new ZonePoint(0, 4)); + path.appendPartialPath( + List.of( + new ZonePoint(1, 3), new ZonePoint(2, 2), new ZonePoint(3, 1), new ZonePoint(4, 0))); + path.appendPartialPath( + List.of(new ZonePoint(5, -1), new ZonePoint(6, -2), new ZonePoint(7, -3))); + + var dto = path.toDto(); + var newPath = Path.fromDto(dto); + + var expectedCellPoints = + List.of( + new ZonePoint(0, 4), + new ZonePoint(1, 3), + new ZonePoint(2, 2), + new ZonePoint(3, 1), + new ZonePoint(4, 0), + new ZonePoint(5, -1), + new ZonePoint(6, -2), + new ZonePoint(7, -3)); + assertIterableEquals(expectedCellPoints, newPath.getCellPath()); + + var expectedWaypoints = List.of(new ZonePoint(0, 4), new ZonePoint(4, 0), new ZonePoint(7, -3)); + assertIterableEquals(expectedWaypoints, newPath.getWayPointList()); + + // Make sure we didn't confuse the point type. + for (var cell : newPath.getCellPath()) { + assertInstanceOf(ZonePoint.class, cell); + } + for (var waypoint : newPath.getWayPointList()) { + assertInstanceOf(ZonePoint.class, waypoint); + } + } + + @Test + @DisplayName("Test that a path of CellPoint can be converted to and from a DTO") + public void testDtoCellPoint() { + var path = new Path(); + path.appendWaypoint(new CellPoint(0, 4)); + path.appendPartialPath( + List.of( + new CellPoint(1, 3), new CellPoint(2, 2), new CellPoint(3, 1), new CellPoint(4, 0))); + path.appendPartialPath( + List.of(new CellPoint(5, -1), new CellPoint(6, -2), new CellPoint(7, -3))); + + var dto = path.toDto(); + var newPath = Path.fromDto(dto); + + var expectedCellPoints = + List.of( + new CellPoint(0, 4), + new CellPoint(1, 3), + new CellPoint(2, 2), + new CellPoint(3, 1), + new CellPoint(4, 0), + new CellPoint(5, -1), + new CellPoint(6, -2), + new CellPoint(7, -3)); + assertIterableEquals(expectedCellPoints, newPath.getCellPath()); + + var expectedWaypoints = List.of(new CellPoint(0, 4), new CellPoint(4, 0), new CellPoint(7, -3)); + assertIterableEquals(expectedWaypoints, newPath.getWayPointList()); + + // Make sure we didn't confuse the point type. + for (var cell : newPath.getCellPath()) { + assertInstanceOf(CellPoint.class, cell); + } + for (var waypoint : newPath.getWayPointList()) { + assertInstanceOf(CellPoint.class, waypoint); + } + } + + // TODO @Nested for derived tests. + + @Nested + class DerivedPathTests { + @Test + @DisplayName( + "Test that a non-snap-to-grid follower path is properly derived from a non-snap-to-grid leader path") + public void testDeriveNonStgFollowingNonStg() { + var path = new Path(); + path.appendWaypoint(new ZonePoint(52, 427)); + path.appendPartialPath( + List.of( + new ZonePoint(116, 364), + new ZonePoint(231, 290), + new ZonePoint(342, 172), + new ZonePoint(429, 65))); + path.appendPartialPath( + List.of(new ZonePoint(502, -91), new ZonePoint(628, -171), new ZonePoint(756, -272))); + var grid = mock(Grid.class); + var leaderToken = mock(Token.class); + when(leaderToken.isSnapToGrid()).thenReturn(false); + // Position agrees with start of path. + when(leaderToken.getX()).thenReturn(52); + when(leaderToken.getY()).thenReturn(427); + var followerToken = mock(Token.class); + when(followerToken.isSnapToGrid()).thenReturn(false); + // Offset a bit from the leader. + when(followerToken.getX()).thenReturn(1012); + when(followerToken.getY()).thenReturn(1184); + + var derived = path.derive(grid, leaderToken, followerToken); + + // All points are used, even though in practice non-STG tokens only have waypoints. + var expectedCellPoints = + List.of( + new ZonePoint(1012, 1184), + new ZonePoint(1076, 1121), + new ZonePoint(1191, 1047), + new ZonePoint(1302, 929), + new ZonePoint(1389, 822), + new ZonePoint(1462, 666), + new ZonePoint(1588, 586), + new ZonePoint(1716, 485)); + assertIterableEquals(expectedCellPoints, derived.getCellPath()); + var expectedWaypoints = + List.of(new ZonePoint(1012, 1184), new ZonePoint(1389, 822), new ZonePoint(1716, 485)); + assertIterableEquals(expectedWaypoints, derived.getWayPointList()); + + for (var cell : derived.getCellPath()) { + assertInstanceOf(ZonePoint.class, cell); + } + for (var waypoint : derived.getWayPointList()) { + assertInstanceOf(ZonePoint.class, waypoint); + } + } + + @Test + @DisplayName( + "Test that a snap-to-grid follower path is properly derived from a snap-to-grid leader path") + public void testDeriveStgFollowingStg() { + var path = new Path(); + path.appendWaypoint(new CellPoint(0, 4)); + path.appendPartialPath( + List.of( + new CellPoint(1, 3), new CellPoint(2, 2), new CellPoint(3, 1), new CellPoint(4, 0))); + path.appendPartialPath( + List.of(new CellPoint(5, -1), new CellPoint(6, -2), new CellPoint(7, -3))); + var grid = mock(Grid.class); + when(grid.convert(new ZonePoint(52, 427))).thenReturn(new CellPoint(0, 4)); + when(grid.convert(new ZonePoint(1012, 1184))).thenReturn(new CellPoint(10, 11)); + + var leaderToken = mock(Token.class); + when(leaderToken.isSnapToGrid()).thenReturn(true); + // Position agrees with start of path. + when(leaderToken.getX()).thenReturn(52); + when(leaderToken.getY()).thenReturn(427); + var followerToken = mock(Token.class); + when(followerToken.isSnapToGrid()).thenReturn(true); + // Offset a bit from the leader. + when(followerToken.getX()).thenReturn(1012); + when(followerToken.getY()).thenReturn(1184); + + var derived = path.derive(grid, leaderToken, followerToken); + + // All points are used, even though in practice non-STG tokens only have waypoints. + var expectedCellPoints = + List.of( + new CellPoint(10, 11), + new CellPoint(11, 10), + new CellPoint(12, 9), + new CellPoint(13, 8), + new CellPoint(14, 7), + new CellPoint(15, 6), + new CellPoint(16, 5), + new CellPoint(17, 4)); + assertIterableEquals(expectedCellPoints, derived.getCellPath()); + var expectedWaypoints = + List.of(new CellPoint(10, 11), new CellPoint(14, 7), new CellPoint(17, 4)); + assertIterableEquals(expectedWaypoints, derived.getWayPointList()); + + for (var cell : derived.getCellPath()) { + assertInstanceOf(CellPoint.class, cell); + } + for (var waypoint : derived.getWayPointList()) { + assertInstanceOf(CellPoint.class, waypoint); + } + } + + @Test + @DisplayName( + "Test that a non-snap-to-grid follower path is properly derived from a snap-to-grid leader path") + public void testDeriveNonStgFollowingStg() { + var path = new Path(); + path.appendWaypoint(new CellPoint(0, 4)); + path.appendPartialPath( + List.of( + new CellPoint(1, 3), new CellPoint(2, 2), new CellPoint(3, 1), new CellPoint(4, 0))); + path.appendPartialPath( + List.of(new CellPoint(5, -1), new CellPoint(6, -2), new CellPoint(7, -3))); + var grid = mock(Grid.class); + when(grid.convert(new CellPoint(0, 4))).thenReturn(new ZonePoint(0, 400)); + when(grid.convert(new CellPoint(1, 3))).thenReturn(new ZonePoint(100, 300)); + when(grid.convert(new CellPoint(2, 2))).thenReturn(new ZonePoint(200, 200)); + when(grid.convert(new CellPoint(3, 1))).thenReturn(new ZonePoint(300, 100)); + when(grid.convert(new CellPoint(4, 0))).thenReturn(new ZonePoint(400, 0)); + when(grid.convert(new CellPoint(5, -1))).thenReturn(new ZonePoint(500, -100)); + when(grid.convert(new CellPoint(6, -2))).thenReturn(new ZonePoint(600, -200)); + when(grid.convert(new CellPoint(7, -3))).thenReturn(new ZonePoint(700, -300)); + var leaderToken = mock(Token.class); + when(leaderToken.isSnapToGrid()).thenReturn(true); + // Position agrees with start of path. + when(leaderToken.getX()).thenReturn(52); + when(leaderToken.getY()).thenReturn(427); + var followerToken = mock(Token.class); + when(followerToken.isSnapToGrid()).thenReturn(false); + // Offset a bit from the leader. + when(followerToken.getX()).thenReturn(1012); + when(followerToken.getY()).thenReturn(1184); + + var derived = path.derive(grid, leaderToken, followerToken); + + // Only the waypoints are used. + var expectedCellPoints = + List.of(new ZonePoint(960, 1157), new ZonePoint(1360, 757), new ZonePoint(1660, 457)); + assertIterableEquals(expectedCellPoints, derived.getCellPath()); + var expectedWaypoints = expectedCellPoints; + assertIterableEquals(expectedWaypoints, derived.getWayPointList()); + + for (var cell : derived.getCellPath()) { + assertInstanceOf(ZonePoint.class, cell); + } + for (var waypoint : derived.getWayPointList()) { + assertInstanceOf(ZonePoint.class, waypoint); + } + } + + @Test + @DisplayName( + "Test that a snap-to-grid follower path is properly derived from a non-snap-to-grid leader path") + public void testDeriveStgFollowingNonStg() { + var path = new Path(); + path.appendWaypoint(new ZonePoint(52, 427)); + path.appendPartialPath( + List.of( + new ZonePoint(116, 364), + new ZonePoint(231, 290), + new ZonePoint(342, 172), + new ZonePoint(429, 65))); + path.appendPartialPath( + List.of(new ZonePoint(502, -91), new ZonePoint(628, -171), new ZonePoint(756, -272))); + var grid = mock(Grid.class); + when(grid.convert(new ZonePoint(1012, 1184))).thenReturn(new CellPoint(10, 11)); + when(grid.convert(new ZonePoint(1389, 822))).thenReturn(new CellPoint(13, 8)); + when(grid.convert(new ZonePoint(1716, 485))).thenReturn(new CellPoint(17, 4)); + var leaderToken = mock(Token.class); + when(leaderToken.isSnapToGrid()).thenReturn(false); + // Position agrees with start of path. + when(leaderToken.getX()).thenReturn(52); + when(leaderToken.getY()).thenReturn(427); + var followerToken = mock(Token.class); + when(followerToken.isSnapToGrid()).thenReturn(true); + // Offset a bit from the leader. + when(followerToken.getX()).thenReturn(1012); + when(followerToken.getY()).thenReturn(1184); + + var derived = path.derive(grid, leaderToken, followerToken); + + // Only waypoints are used directly. The rest are interpolated. + var expectedCellPoints = + List.of( + new CellPoint(10, 11), + new CellPoint(11, 10), + new CellPoint(12, 9), + new CellPoint(13, 8), + new CellPoint(14, 7), + new CellPoint(15, 6), + new CellPoint(16, 5), + new CellPoint(17, 4)); + assertIterableEquals(expectedCellPoints, derived.getCellPath()); + var expectedWaypoints = + List.of(new CellPoint(10, 11), new CellPoint(13, 8), new CellPoint(17, 4)); + assertIterableEquals(expectedWaypoints, derived.getWayPointList()); + + for (var cell : derived.getCellPath()) { + assertInstanceOf(CellPoint.class, cell); + } + for (var waypoint : derived.getWayPointList()) { + assertInstanceOf(CellPoint.class, waypoint); + } + } + + @Test + @DisplayName( + "Test that a snap-to-grid follower path is properly derived from a non-snap-to-grid leader path using the naive walk") + public void testDeriveStgFollowingNonStgWithDifferentSlope() { + var path = new Path(); + path.appendWaypoint(new ZonePoint(52, 427)); + path.appendWaypoint(new ZonePoint(889, 838)); + path.appendWaypoint(new ZonePoint(445, -238)); + var grid = mock(Grid.class); + when(grid.convert(new ZonePoint(1012, 1184))).thenReturn(new CellPoint(10, 11)); + when(grid.convert(new ZonePoint(1849, 1595))).thenReturn(new CellPoint(18, 15)); + when(grid.convert(new ZonePoint(1405, 519))).thenReturn(new CellPoint(14, 5)); + var leaderToken = mock(Token.class); + when(leaderToken.isSnapToGrid()).thenReturn(false); + // Position agrees with start of path. + when(leaderToken.getX()).thenReturn(52); + when(leaderToken.getY()).thenReturn(427); + var followerToken = mock(Token.class); + when(followerToken.isSnapToGrid()).thenReturn(true); + // Offset a bit from the leader. + when(followerToken.getX()).thenReturn(1012); + when(followerToken.getY()).thenReturn(1184); + + var derived = path.derive(grid, leaderToken, followerToken); + + // Only waypoints are used directly. The rest are interpolated. + var expectedCellPoints = + List.of( + new CellPoint(10, 11), + new CellPoint(11, 12), + new CellPoint(12, 13), + new CellPoint(13, 14), + new CellPoint(14, 15), + new CellPoint(15, 15), + new CellPoint(16, 15), + new CellPoint(17, 15), + new CellPoint(18, 15), + new CellPoint(17, 14), + new CellPoint(16, 13), + new CellPoint(15, 12), + new CellPoint(14, 11), + new CellPoint(14, 10), + new CellPoint(14, 9), + new CellPoint(14, 8), + new CellPoint(14, 7), + new CellPoint(14, 6), + new CellPoint(14, 5)); + assertIterableEquals(expectedCellPoints, derived.getCellPath()); + var expectedWaypoints = + List.of(new CellPoint(10, 11), new CellPoint(18, 15), new CellPoint(14, 5)); + assertIterableEquals(expectedWaypoints, derived.getWayPointList()); + + for (var cell : derived.getCellPath()) { + assertInstanceOf(CellPoint.class, cell); + } + for (var waypoint : derived.getWayPointList()) { + assertInstanceOf(CellPoint.class, waypoint); + } + } + + @Test + @DisplayName( + "Test that a snap-to-grid leader's derived path does not include extra waypoints when the path crosses the start or endpoint") + public void testStgPathCrossingItself() { + // Previous implementations of derive() would add waypoints anywhere along a path if the + // position was a waypoint. So a path that crosses itself could be given many more waypoints + // than were actually set. We don't do that anymore, and this test ensures it. + + var path = new Path(); + path.appendWaypoint(new CellPoint(0, 0)); + path.appendPartialPath( + List.of( + new CellPoint(1, 0), new CellPoint(2, 0), new CellPoint(3, 0), new CellPoint(4, 0))); + path.appendPartialPath( + List.of( + new CellPoint(3, 1), new CellPoint(2, 2), new CellPoint(1, 3), new CellPoint(0, 4))); + path.appendPartialPath( + List.of( + new CellPoint(0, 3), + new CellPoint(0, 2), + new CellPoint(0, 1), + // Note: this point is also the first point in the path. + new CellPoint(0, 0), + new CellPoint(0, -1), + new CellPoint(0, -2))); + path.appendPartialPath( + List.of( + new CellPoint(1, -1), + // Note: this last point crosses through the first partial path. + new CellPoint(2, 0))); + var grid = mock(Grid.class); + when(grid.convert(new ZonePoint(50, 50))).thenReturn(new CellPoint(0, 0)); + var leaderToken = mock(Token.class); + when(leaderToken.isSnapToGrid()).thenReturn(true); + // Position agrees with start of path. + when(leaderToken.getX()).thenReturn(50); + when(leaderToken.getY()).thenReturn(50); + + var derived = path.derive(grid, leaderToken, leaderToken); + + // All points are used, even though in practice non-STG tokens only have waypoints. + var expectedCellPoints = + List.of( + new CellPoint(0, 0), + new CellPoint(1, 0), + new CellPoint(2, 0), + new CellPoint(3, 0), + new CellPoint(4, 0), + new CellPoint(3, 1), + new CellPoint(2, 2), + new CellPoint(1, 3), + new CellPoint(0, 4), + new CellPoint(0, 3), + new CellPoint(0, 2), + new CellPoint(0, 1), + new CellPoint(0, 0), + new CellPoint(0, -1), + new CellPoint(0, -2), + new CellPoint(1, -1), + new CellPoint(2, 0)); + assertIterableEquals(expectedCellPoints, derived.getCellPath()); + var expectedWaypoints = + List.of( + // Important that these are exactly the waypoints set, in the orer they were set. + new CellPoint(0, 0), + // Note: No (2, 0) waypoint here. + new CellPoint(4, 0), + new CellPoint(0, 4), + // Note: New (0, 0) waypoint here. + new CellPoint(0, -2), + new CellPoint(2, 0)); + assertIterableEquals(expectedWaypoints, derived.getWayPointList()); + + for (var cell : derived.getCellPath()) { + assertInstanceOf(CellPoint.class, cell); + } + for (var waypoint : derived.getWayPointList()) { + assertInstanceOf(CellPoint.class, waypoint); + } + } + } +}