@@ -637,4 +637,151 @@ public function data_layout_support_flag_renders_consistent_container_hash() {
637637 ),
638638 );
639639 }
640+
641+ /**
642+ * Tests that custom blocks include namespace in layout classnames.
643+ *
644+ * When layout support is enabled for custom blocks, the generated
645+ * layout classname should include the full block namespace to ensure
646+ * that CSS selectors match correctly.
647+ *
648+ * @ticket 63839
649+ * @covers ::wp_render_layout_support_flag
650+ *
651+ * @dataProvider data_layout_classname_with_custom_blocks
652+ */
653+ public function test_layout_classname_includes_namespace_for_custom_blocks ( $ block_name , $ layout_type , $ expected_class , $ should_not_contain ) {
654+ switch_theme ( 'default ' );
655+
656+ register_block_type (
657+ $ block_name ,
658+ array (
659+ 'supports ' => array (
660+ 'layout ' => true ,
661+ ),
662+ )
663+ );
664+
665+ $ block_content = '<div class="wp-block-test"><p>Content</p></div> ' ;
666+ $ block = array (
667+ 'blockName ' => $ block_name ,
668+ 'attrs ' => array (
669+ 'layout ' => array (
670+ 'type ' => $ layout_type ,
671+ ),
672+ ),
673+ );
674+
675+ $ output = wp_render_layout_support_flag ( $ block_content , $ block );
676+
677+ // Assert that the expected class is present.
678+ $ this ->assertStringContainsString ( $ expected_class , $ output );
679+
680+ // Assert that the old buggy class is not present.
681+ $ this ->assertStringNotContainsString ( $ should_not_contain , $ output );
682+
683+ // Clean up the registered block type.
684+ unregister_block_type ( $ block_name );
685+ }
686+
687+ /**
688+ * Data provider for test_layout_classname_includes_namespace_for_custom_blocks.
689+ *
690+ * @return array
691+ */
692+ public function data_layout_classname_with_custom_blocks () {
693+ return array (
694+ 'custom block with constrained layout ' => array (
695+ 'block_name ' => 'foo/bar ' ,
696+ 'layout_type ' => 'constrained ' ,
697+ 'expected_class ' => 'wp-block-foo-bar-is-layout-constrained ' ,
698+ 'should_not_contain ' => 'wp-block-bar-is-layout-constrained ' ,
699+ ),
700+ 'custom block with default layout ' => array (
701+ 'block_name ' => 'foo/bar ' ,
702+ 'layout_type ' => 'default ' ,
703+ 'expected_class ' => 'wp-block-foo-bar-is-layout-flow ' ,
704+ 'should_not_contain ' => 'wp-block-bar-is-layout-flow ' ,
705+ ),
706+ 'custom block with flex layout ' => array (
707+ 'block_name ' => 'foo/bar ' ,
708+ 'layout_type ' => 'flex ' ,
709+ 'expected_class ' => 'wp-block-foo-bar-is-layout-flex ' ,
710+ 'should_not_contain ' => 'wp-block-bar-is-layout-flex ' ,
711+ ),
712+ 'custom block with grid layout ' => array (
713+ 'block_name ' => 'foo/bar ' ,
714+ 'layout_type ' => 'grid ' ,
715+ 'expected_class ' => 'wp-block-foo-bar-is-layout-grid ' ,
716+ 'should_not_contain ' => 'wp-block-bar-is-layout-grid ' ,
717+ ),
718+ );
719+ }
720+
721+ /**
722+ * Tests that core blocks maintain correct layout classnames (regression test).
723+ *
724+ * This test ensures that the fix for custom block layout classnames
725+ * doesn't break the existing behavior for core blocks.
726+ *
727+ * @ticket 63839
728+ * @covers ::wp_render_layout_support_flag
729+ *
730+ * @dataProvider data_layout_classname_with_core_blocks
731+ */
732+ public function test_layout_classname_works_for_core_blocks ( $ block_name , $ layout_type , $ expected_class , $ should_not_contain ) {
733+ switch_theme ( 'default ' );
734+
735+ $ block_content = '<div class="wp-block-test"><p>Content</p></div> ' ;
736+ $ block = array (
737+ 'blockName ' => $ block_name ,
738+ 'attrs ' => array (
739+ 'layout ' => array (
740+ 'type ' => $ layout_type ,
741+ ),
742+ ),
743+ );
744+
745+ $ output = wp_render_layout_support_flag ( $ block_content , $ block );
746+
747+ // Assert that core blocks use only the block name without namespace.
748+ $ this ->assertStringContainsString ( $ expected_class , $ output );
749+
750+ // Assert that core blocks don't include the 'core' namespace prefix.
751+ $ this ->assertStringNotContainsString ( $ should_not_contain , $ output );
752+ }
753+
754+ /**
755+ * Data provider for test_layout_classname_works_for_core_blocks.
756+ *
757+ * @return array
758+ */
759+ public function data_layout_classname_with_core_blocks () {
760+ return array (
761+ 'core group block with constrained layout ' => array (
762+ 'block_name ' => 'core/group ' ,
763+ 'layout_type ' => 'constrained ' ,
764+ 'expected_class ' => 'wp-block-group-is-layout-constrained ' ,
765+ 'should_not_contain ' => 'wp-block-core-group-is-layout-constrained ' ,
766+ ),
767+ 'core cover block with default layout ' => array (
768+ 'block_name ' => 'core/cover ' ,
769+ 'layout_type ' => 'default ' ,
770+ 'expected_class ' => 'wp-block-cover-is-layout-flow ' ,
771+ 'should_not_contain ' => 'wp-block-core-cover-is-layout-flow ' ,
772+ ),
773+ 'core columns block with flex layout ' => array (
774+ 'block_name ' => 'core/columns ' ,
775+ 'layout_type ' => 'flex ' ,
776+ 'expected_class ' => 'wp-block-columns-is-layout-flex ' ,
777+ 'should_not_contain ' => 'wp-block-core-columns-is-layout-flex ' ,
778+ ),
779+ 'core group block with grid layout ' => array (
780+ 'block_name ' => 'core/group ' ,
781+ 'layout_type ' => 'grid ' ,
782+ 'expected_class ' => 'wp-block-group-is-layout-grid ' ,
783+ 'should_not_contain ' => 'wp-block-core-group-is-layout-grid ' ,
784+ ),
785+ );
786+ }
640787}
0 commit comments