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