@@ -1088,6 +1088,216 @@ class XMLContextFactoryTest : KoinTestBase() {
10881088 }
10891089 }
10901090
1091+ @Nested
1092+ @DisplayName(" Praha Topology Improvements (PR #347)" )
1093+ inner class PragueTopologyImprovementsTests {
1094+
1095+ @Test
1096+ @DisplayName(" Praha XML loads with exact element counts after PR #347 additions" )
1097+ fun testPragueExactElementCounts () {
1098+ val xml = getFixtureStream(" praha-hlavni-nadrazi.xml" )
1099+ val context = editingContextFactory.createContext(xml) as EditingContext
1100+
1101+ @Suppress(" UNCHECKED_CAST" )
1102+ val cellGrid = context.getRailWayNetGrid() as RailwayNetGrid <Cell >
1103+
1104+ var inOutCount = 0
1105+ var switchCount = 0
1106+ var semaphoreCount = 0
1107+ for (entry in cellGrid) {
1108+ when (entry.value) {
1109+ is InOut -> inOutCount++
1110+ is RailSwitch -> switchCount++
1111+ is RailSemaphore -> semaphoreCount++
1112+ }
1113+ }
1114+
1115+ // Count unique track blocks via graph (SimpleTrackBlocks are graph edges, not grid cells)
1116+ val seenBlocks = java.util.IdentityHashMap <TrackSection , Unit >()
1117+ val graph = (context as DefaultEditingContext ).getGraph()
1118+ for (node in graph.nodeSet()) {
1119+ for (entry in graph.assignedEdges(node).entries) {
1120+ val edge = entry.value
1121+ if (edge is TrackSection ) {
1122+ seenBlocks[edge] = Unit
1123+ }
1124+ }
1125+ }
1126+ val trackBlockCount = seenBlocks.size
1127+
1128+ assertThat(inOutCount)
1129+ .withMessage(" Praha should have exactly 12 InOut elements after PR #347" )
1130+ .isEqualTo(12 )
1131+ assertThat(switchCount)
1132+ .withMessage(" Praha should have exactly 41 switches after PR #347" )
1133+ .isEqualTo(41 )
1134+ assertThat(semaphoreCount)
1135+ .withMessage(" Praha should have exactly 56 signals after PR #347 (55 original + 1 car train terminal approach)" )
1136+ .isEqualTo(56 )
1137+ assertThat(trackBlockCount)
1138+ .withMessage(" Praha should have exactly 111 track blocks after PR #347" )
1139+ .isEqualTo(111 )
1140+ }
1141+
1142+ @Test
1143+ @DisplayName(" N-Bypass InOut is present at grid (2,20) with entry orientation" )
1144+ fun testPragueNorthBypassInOutPresent () {
1145+ val xml = getFixtureStream(" praha-hlavni-nadrazi.xml" )
1146+ val context = editingContextFactory.createContext(xml)
1147+
1148+ val cell = context.getRailWayNetGrid().getCellAt(2 , 20 )
1149+ assertThat(cell).isNotNull().isInstanceOf(InOut ::class )
1150+
1151+ val inOut = cell as InOut
1152+ assertThat(inOut.getName())
1153+ .withMessage(" N-Bypass InOut should have correct name" )
1154+ .isEqualTo(" N-Bypass" )
1155+ assertThat(inOut.getOrientation())
1156+ .withMessage(" N-Bypass InOut should be an entry point (orientation=false)" )
1157+ .isFalse()
1158+ }
1159+
1160+ @Test
1161+ @DisplayName(" S-CarTrain InOut is present at grid (60,22) with exit orientation" )
1162+ fun testPragueCarTrainTerminalInOutPresent () {
1163+ val xml = getFixtureStream(" praha-hlavni-nadrazi.xml" )
1164+ val context = editingContextFactory.createContext(xml)
1165+
1166+ val cell = context.getRailWayNetGrid().getCellAt(60 , 22 )
1167+ assertThat(cell).isNotNull().isInstanceOf(InOut ::class )
1168+
1169+ val inOut = cell as InOut
1170+ assertThat(inOut.getName())
1171+ .withMessage(" S-CarTrain InOut should have correct name" )
1172+ .isEqualTo(" S-CarTrain" )
1173+ assertThat(inOut.getOrientation())
1174+ .withMessage(" S-CarTrain InOut should be an exit point (orientation=true)" )
1175+ .isTrue()
1176+ }
1177+
1178+ @Test
1179+ @DisplayName(" Bypass route N-Bypass to S-Bypass is navigable" )
1180+ fun testPragueBypassRouteNavigable () {
1181+ val xml = getFixtureStream(" praha-hlavni-nadrazi.xml" )
1182+ val context = editingContextFactory.createContext(xml) as EditingContext
1183+
1184+ var nBypass: InOut ? = null
1185+ var sBypass: InOut ? = null
1186+ for (entry in context.getRailWayNetGrid()) {
1187+ val cell = entry.value
1188+ if (cell is InOut ) {
1189+ when (cell.getName()) {
1190+ " N-Bypass" -> nBypass = cell
1191+ " S-Bypass" -> sBypass = cell
1192+ }
1193+ }
1194+ }
1195+
1196+ assertThat(nBypass)
1197+ .withMessage(" N-Bypass InOut should exist in Praha XML" )
1198+ .isNotNull()
1199+ assertThat(sBypass)
1200+ .withMessage(" S-Bypass InOut should exist in Praha XML" )
1201+ .isNotNull()
1202+
1203+ assertThat(existPath(nBypass!! , sBypass!! , context as DefaultEditingContext ))
1204+ .withMessage(" Path should exist from N-Bypass to S-Bypass" )
1205+ .isTrue()
1206+ }
1207+
1208+ @Test
1209+ @DisplayName(" S-CarTrain terminal is reachable from the N-Bypass entry" )
1210+ fun testPragueCarTrainTerminalAccessible () {
1211+ val xml = getFixtureStream(" praha-hlavni-nadrazi.xml" )
1212+ val context = editingContextFactory.createContext(xml) as EditingContext
1213+
1214+ // S-CarTrain is accessible from N-Bypass via the Y=20/Y=22 bypass corridor
1215+ var nBypass: InOut ? = null
1216+ var sCarTrain: InOut ? = null
1217+ for (entry in context.getRailWayNetGrid()) {
1218+ val cell = entry.value
1219+ if (cell is InOut ) {
1220+ when (cell.getName()) {
1221+ " N-Bypass" -> nBypass = cell
1222+ " S-CarTrain" -> sCarTrain = cell
1223+ }
1224+ }
1225+ }
1226+
1227+ assertThat(nBypass)
1228+ .withMessage(" N-Bypass InOut should exist in Praha XML" )
1229+ .isNotNull()
1230+ assertThat(sCarTrain)
1231+ .withMessage(" S-CarTrain InOut should exist in Praha XML" )
1232+ .isNotNull()
1233+
1234+ assertThat(existPath(nBypass!! , sCarTrain!! , context as DefaultEditingContext ))
1235+ .withMessage(" Path should exist from N-Bypass to S-CarTrain via bypass corridor (Y=20→Y=22)" )
1236+ .isTrue()
1237+ }
1238+
1239+ @Test
1240+ @DisplayName(" Switch orientations at new bypass infrastructure are railway-domain correct" )
1241+ fun testPragueSwitchOrientationsAtNewInfrastructure () {
1242+ val xml = getFixtureStream(" praha-hlavni-nadrazi.xml" )
1243+ val context = editingContextFactory.createContext(xml)
1244+
1245+ val switchAt6x20 = context.getRailWayNetGrid().getCellAt(6 , 20 )
1246+ assertThat(switchAt6x20).isNotNull().isInstanceOf(RailSwitch ::class )
1247+ assertThat((switchAt6x20 as RailSwitch ).type)
1248+ .withMessage(" Bypass entry switch at (6,20) must be SIMPLE_LEFT_TRUE for correct bypass diverge" )
1249+ .isEqualTo(RailSwitch .Type .SIMPLE_LEFT_TRUE )
1250+
1251+ val switchAt52x20 = context.getRailWayNetGrid().getCellAt(52 , 20 )
1252+ assertThat(switchAt52x20).isNotNull().isInstanceOf(RailSwitch ::class )
1253+ assertThat((switchAt52x20 as RailSwitch ).type)
1254+ .withMessage(" Bypass exit switch at (52,20) must be SIMPLE_RIGHT_TRUE to allow diverge toward Y=22" )
1255+ .isEqualTo(RailSwitch .Type .SIMPLE_RIGHT_TRUE )
1256+ }
1257+
1258+ /* *
1259+ * Path existence check for Praha topology improvement tests.
1260+ * Reuses the BFS approach from ComplexStationConfigurationTests.
1261+ */
1262+ private fun existPath (
1263+ from : InOut ,
1264+ to : InOut ,
1265+ context : DefaultEditingContext
1266+ ): Boolean {
1267+ val fromLoc = context.getRailWayNetGrid().getLocation(from) ? : return false
1268+ val toLoc = context.getRailWayNetGrid().getLocation(to) ? : return false
1269+ if (fromLoc == toLoc) return true
1270+
1271+ val graph = context.getGraph()
1272+ val visited = mutableSetOf<Point >()
1273+ val queue = mutableListOf (fromLoc)
1274+
1275+ while (queue.isNotEmpty()) {
1276+ val current = queue.removeFirst()
1277+ if (current in visited) continue
1278+ visited.add(current)
1279+ if (current == toLoc) return true
1280+
1281+ val edges = graph.assignedEdges(current)
1282+ for (entry in edges.entries) {
1283+ val trackBlock = entry.value
1284+ if (trackBlock !is TrackSection ) continue
1285+
1286+ val ends = trackBlock.ends()
1287+ for (pathSeparator in ends) {
1288+ if (pathSeparator !is cz.vutbr.fit.interlockSim.objects.cells.NodeCell ) continue
1289+
1290+ val endLocation = context.getRailWayNetGrid().getLocation(pathSeparator) ? : continue
1291+ if (endLocation != current && endLocation !in visited) {
1292+ queue.add(endLocation)
1293+ }
1294+ }
1295+ }
1296+ }
1297+ return false
1298+ }
1299+ }
1300+
10911301 @Nested
10921302 @DisplayName(" Name attribute persistence and validation (Issue #306)" )
10931303 inner class NameAttributeTests {
0 commit comments