@@ -900,7 +900,7 @@ public function test_page_data_var_name_has_pd_prefix_and_slugified_title(): voi
900900 $ this ->assertStringContainsString ('$pd_My_Great_Article ' , $ content );
901901 }
902902
903- public function test_page_data_with_null_title_uses_pageData_fallback_var_name (): void
903+ public function test_page_data_with_null_title_uses_page_data_fallback_var_name (): void
904904 {
905905 $ pd = new _TestArticleData ();
906906 $ pd ->setTitle (null ); // explicitly null — triggers the ?? 'pageData' fallback
@@ -1153,7 +1153,7 @@ public function test_export_array_format_multiple_items_comma_separated(): void
11531153 public function test_success_message_written_to_output (): void
11541154 {
11551155 $ command = $ this ->makeCommand ($ this ->emptyRegistry ());
1156- $ tester = new \ Symfony \ Component \ Console \ Tester \ CommandTester ($ command );
1156+ $ tester = new CommandTester ($ command );
11571157 $ tester ->execute (['--output ' => $ this ->outputFile ]);
11581158
11591159 $ display = $ tester ->getDisplay ();
@@ -1249,4 +1249,243 @@ public function test_page_with_title_and_children_emits_both_title_and_nested_in
12491249 $ this ->assertStringContainsString ("\$page->title('Parent Title') " , $ content );
12501250 $ this ->assertStringContainsString ('->nested( ' , $ content );
12511251 }
1252+
1253+ // --- Coalesce mutant killers: child indexed via getParentPage() fallback ---
1254+
1255+ public function test_child_page_linked_via_parent_page_appears_in_nested_closure (): void
1256+ {
1257+ $ layout = $ this ->makeLayout ('main ' , 'CwaLayoutPrimary ' );
1258+ $ parent = $ this ->makePage ('parent ' , 'CwaPageParent ' , $ layout , $ this ->makeRoute ('/parent ' ));
1259+ $ child = $ this ->makePage ('child ' , 'CwaPageChild ' , $ layout , $ this ->makeRoute ('/parent/child ' ));
1260+ $ child ->setParentPage ($ parent );
1261+
1262+ $ this ->runCommand (
1263+ $ this ->registryWith (layouts: [$ layout ], pages: [$ parent , $ child ]),
1264+ $ this ->outputFile ,
1265+ );
1266+
1267+ $ content = file_get_contents ($ this ->outputFile );
1268+ $ this ->assertStringContainsString ('->nested( ' , $ content );
1269+ $ this ->assertStringContainsString ("'child' " , $ content );
1270+ }
1271+
1272+ public function test_child_page_data_linked_via_parent_page_appears_in_nested_closure (): void
1273+ {
1274+ $ layout = $ this ->makeLayout ('main ' , 'CwaLayoutPrimary ' );
1275+ $ templatePage = $ this ->makePage ('tmpl ' , 'CwaPageTmpl ' , $ layout , null , true );
1276+ $ parent = $ this ->makePage ('parent ' , 'CwaPageParent ' , $ layout , $ this ->makeRoute ('/parent ' ));
1277+
1278+ $ childPd = new _TestArticleData ();
1279+ $ childPd ->setTitle ('Child Article ' );
1280+ $ childPd ->setParentPage ($ parent );
1281+ $ childPd ->setRoute ($ this ->makeRoute ('/parent/child ' ));
1282+ $ childPd ->page = $ templatePage ;
1283+
1284+ $ this ->runCommand (
1285+ $ this ->registryWith (layouts: [$ layout ], pages: [$ parent , $ templatePage ], pageData: [$ childPd ]),
1286+ $ this ->outputFile ,
1287+ );
1288+
1289+ $ content = file_get_contents ($ this ->outputFile );
1290+ $ this ->assertStringContainsString ('->nested( ' , $ content );
1291+ $ this ->assertStringContainsString ('Child Article ' , $ content );
1292+ }
1293+
1294+ // --- stripUiPrefix edge cases ---
1295+
1296+ public function test_layout_with_null_ui_component_emits_null (): void
1297+ {
1298+ $ layout = $ this ->makeLayout ('main ' , '' );
1299+ $ layout ->uiComponent = null ;
1300+
1301+ $ this ->runCommand ($ this ->registryWith (layouts: [$ layout ]), $ this ->outputFile );
1302+
1303+ $ content = file_get_contents ($ this ->outputFile );
1304+ $ this ->assertStringContainsString ('->layout( ' , $ content );
1305+ $ this ->assertStringContainsString ('NULL ' , $ content );
1306+ }
1307+
1308+ public function test_layout_with_non_cwa_prefixed_ui_component_emits_as_is (): void
1309+ {
1310+ $ layout = $ this ->makeLayout ('main ' , 'MyCustomLayout ' );
1311+
1312+ $ this ->runCommand ($ this ->registryWith (layouts: [$ layout ]), $ this ->outputFile );
1313+
1314+ $ content = file_get_contents ($ this ->outputFile );
1315+ $ this ->assertStringContainsString ("'MyCustomLayout' " , $ content );
1316+ }
1317+
1318+ public function test_page_with_cwa_page_prefixed_ui_component_strips_prefix (): void
1319+ {
1320+ $ layout = $ this ->makeLayout ('main ' , 'CwaLayoutPrimary ' );
1321+ $ route = $ this ->makeRoute ('/blog ' );
1322+ $ page = $ this ->makePage ('blog ' , 'CwaPageBlog ' , $ layout , $ route );
1323+
1324+ $ this ->runCommand ($ this ->registryWith (layouts: [$ layout ], pages: [$ page ]), $ this ->outputFile );
1325+
1326+ $ content = file_get_contents ($ this ->outputFile );
1327+ $ this ->assertStringContainsString ("'Blog' " , $ content );
1328+ $ this ->assertStringNotContainsString ("'CwaPageBlog' " , $ content );
1329+ }
1330+
1331+ public function test_page_with_non_cwa_prefixed_ui_component_emits_as_is (): void
1332+ {
1333+ $ layout = $ this ->makeLayout ('main ' , 'CwaLayoutPrimary ' );
1334+ $ route = $ this ->makeRoute ('/home ' );
1335+ $ page = $ this ->makePage ('home ' , 'CustomPageTemplate ' , $ layout , $ route );
1336+
1337+ $ this ->runCommand ($ this ->registryWith (layouts: [$ layout ], pages: [$ page ]), $ this ->outputFile );
1338+
1339+ $ content = file_get_contents ($ this ->outputFile );
1340+ $ this ->assertStringContainsString ("'CustomPageTemplate' " , $ content );
1341+ }
1342+
1343+ // --- continue vs break in top-level iteration ---
1344+
1345+ public function test_second_top_level_page_is_emitted_when_first_page_in_array_is_a_child (): void
1346+ {
1347+ $ layout = $ this ->makeLayout ('main ' , 'CwaLayoutPrimary ' );
1348+ $ parent = $ this ->makePage ('parent ' , 'CwaPageParent ' , $ layout , $ this ->makeRoute ('/parent ' ));
1349+ $ child = $ this ->makePage ('child ' , 'CwaPageChild ' , $ layout , $ this ->makeRoute ('/parent/child ' ));
1350+ $ child ->setParentPage ($ parent );
1351+ $ other = $ this ->makePage ('other ' , 'CwaPageOther ' , $ layout , $ this ->makeRoute ('/other ' ));
1352+
1353+ // child is first in array — break mutant would stop after skipping it, losing 'other'
1354+ $ this ->runCommand (
1355+ $ this ->registryWith (layouts: [$ layout ], pages: [$ child , $ parent , $ other ]),
1356+ $ this ->outputFile ,
1357+ );
1358+
1359+ $ content = file_get_contents ($ this ->outputFile );
1360+ $ this ->assertStringContainsString ("'other' " , $ content );
1361+ $ this ->assertStringContainsString ("'parent' " , $ content );
1362+ }
1363+
1364+ public function test_second_top_level_page_data_is_emitted_when_first_is_a_child (): void
1365+ {
1366+ $ parentPd = new _TestArticleData ();
1367+ $ parentPd ->setTitle ('Parent ' );
1368+ $ parentPd ->setRoute ($ this ->makeRoute ('/parent ' ));
1369+
1370+ $ childPd = new _TestArticleData ();
1371+ $ childPd ->setTitle ('Child ' );
1372+ $ childPd ->setParentPageData ($ parentPd );
1373+ $ childPd ->setRoute ($ this ->makeRoute ('/parent/child ' ));
1374+
1375+ $ otherPd = new _TestArticleData ();
1376+ $ otherPd ->setTitle ('Other ' );
1377+ $ otherPd ->setRoute ($ this ->makeRoute ('/other ' ));
1378+
1379+ // child first — break mutant stops after skipping, losing 'other'
1380+ $ this ->runCommand (
1381+ $ this ->registryWith (pageData: [$ childPd , $ parentPd , $ otherPd ]),
1382+ $ this ->outputFile ,
1383+ );
1384+
1385+ $ content = file_get_contents ($ this ->outputFile );
1386+ $ this ->assertStringContainsString ("'Other' " , $ content );
1387+ $ this ->assertStringContainsString ("'Parent' " , $ content );
1388+ }
1389+
1390+ // --- page configure closure triggered by title alone ---
1391+
1392+ public function test_page_with_title_only_emits_configure_closure_with_title (): void
1393+ {
1394+ $ layout = $ this ->makeLayout ('main ' , 'CwaLayoutPrimary ' );
1395+ $ page = $ this ->makePage ('home ' , 'CwaPageHome ' , $ layout , $ this ->makeRoute ('/ ' ));
1396+ $ page ->setTitle ('Welcome ' );
1397+
1398+ $ this ->runCommand ($ this ->registryWith (layouts: [$ layout ], pages: [$ page ]), $ this ->outputFile );
1399+
1400+ $ content = file_get_contents ($ this ->outputFile );
1401+ $ this ->assertStringContainsString ('configure: ' , $ content );
1402+ $ this ->assertStringContainsString ("->title('Welcome') " , $ content );
1403+ }
1404+
1405+ // --- route + uiClassNames both appear in page args (kills Assignment .= → = mutant) ---
1406+
1407+ public function test_page_ui_class_names_appended_alongside_route_in_args (): void
1408+ {
1409+ $ layout = $ this ->makeLayout ('main ' , 'CwaLayoutPrimary ' );
1410+ $ route = $ this ->makeRoute ('/home ' , 'home-page ' );
1411+ $ page = $ this ->makePage ('home ' , 'CwaPageHome ' , $ layout , $ route );
1412+ $ page ->uiClassNames = ['hero ' ];
1413+
1414+ $ this ->runCommand ($ this ->registryWith (layouts: [$ layout ], pages: [$ page ]), $ this ->outputFile );
1415+
1416+ $ content = file_get_contents ($ this ->outputFile );
1417+ $ this ->assertStringContainsString ("'/home' " , $ content );
1418+ $ this ->assertStringContainsString ('uiClassNames ' , $ content );
1419+ $ this ->assertStringContainsString ("'hero' " , $ content );
1420+ }
1421+
1422+ // --- group with two positions emits both (kills posCode concat mutants) ---
1423+
1424+ public function test_group_with_two_components_emits_both_in_closure (): void
1425+ {
1426+ $ layout = $ this ->makeLayout ('main ' , 'CwaLayoutPrimary ' );
1427+ $ route = $ this ->makeRoute ('/home ' );
1428+ $ page = $ this ->makePage ('home ' , 'CwaPageHome ' , $ layout , $ route );
1429+
1430+ $ comp1 = new _TestHtmlComponent ();
1431+ $ comp1 ->html = 'first ' ;
1432+ $ comp2 = new _TestHtmlComponent ();
1433+ $ comp2 ->html = 'second ' ;
1434+
1435+ $ pos1 = new ComponentPosition ();
1436+ $ pos1 ->setComponent ($ comp1 );
1437+ $ pos1 ->setSortValue (10 );
1438+ $ pos2 = new ComponentPosition ();
1439+ $ pos2 ->setComponent ($ comp2 );
1440+ $ pos2 ->setSortValue (20 );
1441+
1442+ $ group = new ComponentGroup ();
1443+ $ group ->reference = 'page-home-primary ' ;
1444+ $ group ->location = '/_/pages/some-uuid ' ;
1445+ $ group ->componentPositions ->add ($ pos1 );
1446+ $ group ->componentPositions ->add ($ pos2 );
1447+ $ page ->getComponentGroups ()->add ($ group );
1448+
1449+ $ this ->runCommand ($ this ->registryWith (layouts: [$ layout ], pages: [$ page ]), $ this ->outputFile );
1450+
1451+ $ content = file_get_contents ($ this ->outputFile );
1452+ $ this ->assertStringContainsString ('first ' , $ content );
1453+ $ this ->assertStringContainsString ('second ' , $ content );
1454+ }
1455+
1456+ // --- component uiComponent and own-property both emitted (kills code .= Assignment/Concat mutants) ---
1457+
1458+ public function test_component_class_instantiation_uicomponent_and_property_all_appear (): void
1459+ {
1460+ $ layout = $ this ->makeLayout ('main ' , 'CwaLayoutPrimary ' );
1461+ $ route = $ this ->makeRoute ('/home ' );
1462+ $ page = $ this ->makePage ('home ' , 'CwaPageHome ' , $ layout , $ route );
1463+
1464+ $ comp = new _TestHtmlComponent ();
1465+ $ comp ->uiComponent = 'FancyRenderer ' ;
1466+ $ comp ->uiClassNames = ['bold ' , 'dark ' ];
1467+ $ comp ->html = 'body text ' ;
1468+
1469+ $ pos = new ComponentPosition ();
1470+ $ pos ->setComponent ($ comp );
1471+ $ pos ->setSortValue (10 );
1472+
1473+ $ group = new ComponentGroup ();
1474+ $ group ->reference = 'page-home-primary ' ;
1475+ $ group ->location = '/_/pages/some-uuid ' ;
1476+ $ group ->componentPositions ->add ($ pos );
1477+ $ page ->getComponentGroups ()->add ($ group );
1478+
1479+ $ this ->runCommand ($ this ->registryWith (layouts: [$ layout ], pages: [$ page ]), $ this ->outputFile );
1480+
1481+ $ content = file_get_contents ($ this ->outputFile );
1482+ // class instantiation must appear (kills Assignment line 174/177 that resets $code)
1483+ $ this ->assertStringContainsString ('new _TestHtmlComponent() ' , $ content );
1484+ // full uiComponent assignment (kills Concat/ConcatOperandRemoval on line ~174)
1485+ $ this ->assertStringContainsString ("->uiComponent = 'FancyRenderer'; " , $ content );
1486+ // full uiClassNames assignment (kills Concat/ConcatOperandRemoval/Assignment on line ~177)
1487+ $ this ->assertStringContainsString ("->uiClassNames = ['bold', 'dark']; " , $ content );
1488+ // full own-property assignment including value (kills ConcatOperandRemoval on line ~189)
1489+ $ this ->assertStringContainsString ("->html = 'body text'; " , $ content );
1490+ }
12521491}
0 commit comments