diff --git a/core/.phpstan-baseline.php b/core/.phpstan-baseline.php index a231669f9cdc94bc5e0031d780ebd05d994729a4..13fa6dc2a554fd1753dd15de80c323774f332c7d 100644 --- a/core/.phpstan-baseline.php +++ b/core/.phpstan-baseline.php @@ -13323,12 +13323,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/comment/tests/src/Functional/CommentNonNodeTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\CommentNonNodeTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/comment/tests/src/Functional/CommentNonNodeTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\comment\\\\Functional\\\\CommentNonNodeTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -14613,12 +14607,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/contact/tests/src/Functional/ContactPersonalTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\ContactSitewideTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/contact/tests/src/Functional/ContactSitewideTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\contact\\\\Functional\\\\ContactSitewideTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -15927,12 +15915,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/content_translation/tests/src/Functional/ContentTranslationSettingsTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\content_translation\\\\Functional\\\\ContentTranslationSettingsTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/content_translation/tests/src/Functional/ContentTranslationSettingsTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\content_translation\\\\Functional\\\\ContentTranslationSettingsTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -16839,12 +16821,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/field/tests/modules/field_test/src/Plugin/Field/FieldWidget/TestFieldWidgetMultiple.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\EntityReference\\\\EntityReferenceAdminTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field/tests/src/Functional/EntityReference/EntityReferenceAdminTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\Functional\\\\EntityReference\\\\EntityReferenceAdminTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -17145,40 +17121,34 @@ 'count' => 1, 'path' => __DIR__ . '/modules/field/tests/src/Functional/Views/FieldTestBase.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\FunctionalJavascript\\\\EntityReference\\\\EntityReferenceAdminTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\FunctionalJavascript\\\\EntityReference\\\\EntityReferenceAdminTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php', ]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\FunctionalJavascript\\\\EntityReference\\\\EntityReferenceAdminTest\\:\\:assertFieldExistsOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php', ]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\FunctionalJavascript\\\\EntityReference\\\\EntityReferenceAdminTest\\:\\:fieldUIAddExistingField\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php', ]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\FunctionalJavascript\\\\EntityReference\\\\EntityReferenceAdminTest\\:\\:fieldUIAddNewField\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php', ]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\field\\\\FunctionalJavascript\\\\EntityReference\\\\EntityReferenceAdminTest\\:\\:fieldUIDeleteField\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', - 'count' => 1, + 'count' => 2, 'path' => __DIR__ . '/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php', ]; $ignoreErrors[] = [ @@ -17788,13 +17758,7 @@ 'path' => __DIR__ . '/modules/field_ui/src/Form/FieldStorageAddForm.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\field_ui\\\\Form\\\\FieldStorageAddForm\\:\\:validateFieldType\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field_ui/src/Form/FieldStorageAddForm.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\field_ui\\\\Form\\\\FieldStorageAddForm\\:\\:validateGroupOrField\\(\\) has no return type specified\\.$#', + 'message' => '#^Method Drupal\\\\field_ui\\\\Form\\\\FieldStorageAddForm\\:\\:validateForm\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, 'path' => __DIR__ . '/modules/field_ui/src/Form/FieldStorageAddForm.php', @@ -17865,12 +17829,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/field_ui/src/Routing/RouteSubscriber.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\Functional\\\\FieldUIDeleteTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field_ui/tests/src/Functional/FieldUIDeleteTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\Functional\\\\FieldUIDeleteTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -17901,12 +17859,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/field_ui/tests/src/Functional/FieldUIDeleteTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\Functional\\\\ManageDisplayTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field_ui/tests/src/Functional/ManageDisplayTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\Functional\\\\ManageDisplayTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -17937,12 +17889,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/field_ui/tests/src/Functional/ManageDisplayTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\Functional\\\\ManageFieldsFunctionalTestBase\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTestBase.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\Functional\\\\ManageFieldsFunctionalTestBase\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -17979,12 +17925,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTestBase.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\Functional\\\\ManageFieldsTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/field_ui/tests/src/Functional/ManageFieldsTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\Functional\\\\ManageFieldsTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -18016,16 +17956,40 @@ 'path' => __DIR__ . '/modules/field_ui/tests/src/Functional/ManageFieldsTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\FunctionalJavascript\\\\DisplayModeBundleSelectionTest\\:\\:providerBundleSelection\\(\\) has no return type specified\\.$#', + 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\FunctionalJavascript\\\\DefaultValueWidgetTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, - 'path' => __DIR__ . '/modules/field_ui/tests/src/FunctionalJavascript/DisplayModeBundleSelectionTest.php', + 'path' => __DIR__ . '/modules/field_ui/tests/src/FunctionalJavascript/DefaultValueWidgetTest.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\FunctionalJavascript\\\\ManageDisplayTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', + 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\FunctionalJavascript\\\\DefaultValueWidgetTest\\:\\:assertFieldExistsOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', 'count' => 1, - 'path' => __DIR__ . '/modules/field_ui/tests/src/FunctionalJavascript/ManageDisplayTest.php', + 'path' => __DIR__ . '/modules/field_ui/tests/src/FunctionalJavascript/DefaultValueWidgetTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\FunctionalJavascript\\\\DefaultValueWidgetTest\\:\\:fieldUIAddExistingField\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/field_ui/tests/src/FunctionalJavascript/DefaultValueWidgetTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\FunctionalJavascript\\\\DefaultValueWidgetTest\\:\\:fieldUIAddNewField\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/field_ui/tests/src/FunctionalJavascript/DefaultValueWidgetTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\FunctionalJavascript\\\\DefaultValueWidgetTest\\:\\:fieldUIDeleteField\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/field_ui/tests/src/FunctionalJavascript/DefaultValueWidgetTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\FunctionalJavascript\\\\DisplayModeBundleSelectionTest\\:\\:providerBundleSelection\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/field_ui/tests/src/FunctionalJavascript/DisplayModeBundleSelectionTest.php', ]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\FunctionalJavascript\\\\ManageDisplayTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', @@ -18057,6 +18021,36 @@ 'count' => 1, 'path' => __DIR__ . '/modules/field_ui/tests/src/FunctionalJavascript/ManageDisplayTest.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\FunctionalJavascript\\\\ManageFieldsTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\FunctionalJavascript\\\\ManageFieldsTest\\:\\:assertFieldExistsOnOverview\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\FunctionalJavascript\\\\ManageFieldsTest\\:\\:fieldUIAddExistingField\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\FunctionalJavascript\\\\ManageFieldsTest\\:\\:fieldUIAddNewField\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\FunctionalJavascript\\\\ManageFieldsTest\\:\\:fieldUIDeleteField\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\field_ui\\\\Unit\\\\FieldUiTableTest\\:\\:providerTestReduceOrder\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -18459,12 +18453,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/file/tests/modules/file_validator_test/src/EventSubscriber/FileSanitizationEventSubscriber.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\Functional\\\\FileFieldDisplayTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/file/tests/src/Functional/FileFieldDisplayTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\Functional\\\\FileFieldDisplayTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -18549,12 +18537,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/file/tests/src/Functional/FileFieldWidgetTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\Functional\\\\FileFieldWidgetTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/file/tests/src/Functional/FileFieldWidgetTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\Functional\\\\FileFieldWidgetTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -18765,12 +18747,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/file/tests/src/FunctionalJavascript/FileFieldValidateTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\FunctionalJavascript\\\\FileFieldWidgetTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/file/tests/src/FunctionalJavascript/FileFieldWidgetTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\file\\\\FunctionalJavascript\\\\FileFieldWidgetTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -22473,12 +22449,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/layout_builder/tests/src/Functional/LayoutBuilderFieldBlockEntityReferenceCacheTagsTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\layout_builder\\\\Functional\\\\LayoutBuilderTestBase\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/layout_builder/tests/src/Functional/LayoutBuilderTestBase.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\layout_builder\\\\Functional\\\\LayoutBuilderTestBase\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -23049,12 +23019,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/link/tests/src/Functional/LinkFieldTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\link\\\\Functional\\\\LinkFieldUITest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/link/tests/src/Functional/LinkFieldUITest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\link\\\\Functional\\\\LinkFieldUITest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -24207,12 +24171,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/media/tests/src/Functional/MediaTypeCreationTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\MediaUiFunctionalTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/media/tests/src/Functional/MediaUiFunctionalTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\MediaUiFunctionalTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -24243,12 +24201,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/media/tests/src/Functional/MediaUiFunctionalTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\MediaUiReferenceWidgetTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/media/tests/src/Functional/MediaUiReferenceWidgetTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\media\\\\Functional\\\\MediaUiReferenceWidgetTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -28569,6 +28521,18 @@ 'count' => 1, 'path' => __DIR__ . '/modules/options/src/Plugin/views/filter/ListField.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\options\\\\FunctionalJavascript\\\\OptionsFieldUIAllowedValuesTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/options/tests/src/FunctionalJavascript/OptionsFieldUIAllowedValuesTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\options\\\\FunctionalJavascript\\\\OptionsFieldUIAllowedValuesTest\\:\\:assertFieldExistsOnOverview\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/options/tests/src/FunctionalJavascript/OptionsFieldUIAllowedValuesTest.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\options\\\\FunctionalJavascript\\\\OptionsFieldUIAllowedValuesTest\\:\\:assertNodeFormOrder\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -28587,6 +28551,54 @@ 'count' => 1, 'path' => __DIR__ . '/modules/options/tests/src/FunctionalJavascript/OptionsFieldUIAllowedValuesTest.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\options\\\\FunctionalJavascript\\\\OptionsFieldUIAllowedValuesTest\\:\\:fieldUIAddExistingField\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/options/tests/src/FunctionalJavascript/OptionsFieldUIAllowedValuesTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\options\\\\FunctionalJavascript\\\\OptionsFieldUIAllowedValuesTest\\:\\:fieldUIAddNewField\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/options/tests/src/FunctionalJavascript/OptionsFieldUIAllowedValuesTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\options\\\\FunctionalJavascript\\\\OptionsFieldUIAllowedValuesTest\\:\\:fieldUIDeleteField\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/options/tests/src/FunctionalJavascript/OptionsFieldUIAllowedValuesTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\options\\\\FunctionalJavascript\\\\OptionsFieldUITest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\options\\\\FunctionalJavascript\\\\OptionsFieldUITest\\:\\:assertFieldExistsOnOverview\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\options\\\\FunctionalJavascript\\\\OptionsFieldUITest\\:\\:fieldUIAddExistingField\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\options\\\\FunctionalJavascript\\\\OptionsFieldUITest\\:\\:fieldUIAddNewField\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\options\\\\FunctionalJavascript\\\\OptionsFieldUITest\\:\\:fieldUIDeleteField\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\options\\\\Kernel\\\\Views\\\\OptionsTestBase\\:\\:mockStandardInstall\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -29331,6 +29343,36 @@ 'count' => 1, 'path' => __DIR__ . '/modules/responsive_image/tests/src/Functional/Rest/ResponsiveImageStyleXmlCookieTest.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\responsive_image\\\\FunctionalJavascript\\\\ResponsiveImageFieldUiTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/responsive_image/tests/src/FunctionalJavascript/ResponsiveImageFieldUiTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\responsive_image\\\\FunctionalJavascript\\\\ResponsiveImageFieldUiTest\\:\\:assertFieldExistsOnOverview\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/responsive_image/tests/src/FunctionalJavascript/ResponsiveImageFieldUiTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\responsive_image\\\\FunctionalJavascript\\\\ResponsiveImageFieldUiTest\\:\\:fieldUIAddExistingField\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/responsive_image/tests/src/FunctionalJavascript/ResponsiveImageFieldUiTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\responsive_image\\\\FunctionalJavascript\\\\ResponsiveImageFieldUiTest\\:\\:fieldUIAddNewField\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/responsive_image/tests/src/FunctionalJavascript/ResponsiveImageFieldUiTest.php', +]; +$ignoreErrors[] = [ + 'message' => '#^Method Drupal\\\\Tests\\\\responsive_image\\\\FunctionalJavascript\\\\ResponsiveImageFieldUiTest\\:\\:fieldUIDeleteField\\(\\) has no return type specified\\.$#', + 'identifier' => 'missingType.return', + 'count' => 1, + 'path' => __DIR__ . '/modules/responsive_image/tests/src/FunctionalJavascript/ResponsiveImageFieldUiTest.php', +]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\rest\\\\Entity\\\\ConfigDependencies\\:\\:create\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -30483,12 +30525,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/search/tests/src/Functional/SearchPageCacheTagsTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\search\\\\Functional\\\\SearchPageCacheTagsTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/search/tests/src/Functional/SearchPageCacheTagsTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\search\\\\Functional\\\\SearchPageCacheTagsTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -35249,12 +35285,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/system/tests/src/Functional/Entity/EntityFormTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Entity\\\\EntityReferenceFieldCreationTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/src/Functional/Entity/EntityReferenceFieldCreationTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\Entity\\\\EntityReferenceFieldCreationTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -35773,12 +35803,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/system/tests/src/Functional/System/CronRunTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\System\\\\DateTimeTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/system/tests/src/Functional/System/DateTimeTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\system\\\\Functional\\\\System\\\\DateTimeTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -49183,12 +49207,6 @@ 'count' => 1, 'path' => __DIR__ . '/modules/workspaces/tests/src/Functional/WorkspaceSwitcherTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Functional\\\\WorkspaceTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/modules/workspaces/tests/src/Functional/WorkspaceTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\Tests\\\\workspaces\\\\Functional\\\\WorkspaceTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', @@ -49909,12 +49927,6 @@ 'count' => 1, 'path' => __DIR__ . '/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxFormPageCacheTest.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method Drupal\\\\FunctionalJavascriptTests\\\\Ajax\\\\AjaxMaintenanceModeTest\\:\\:assertFieldDoesNotExist\\(\\) has no return type specified\\.$#', - 'identifier' => 'missingType.return', - 'count' => 1, - 'path' => __DIR__ . '/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxMaintenanceModeTest.php', -]; $ignoreErrors[] = [ 'message' => '#^Method Drupal\\\\FunctionalJavascriptTests\\\\Ajax\\\\AjaxMaintenanceModeTest\\:\\:assertFieldDoesNotExistOnOverview\\(\\) has no return type specified\\.$#', 'identifier' => 'missingType.return', diff --git a/core/misc/field-list-keyboard-navigation.js b/core/misc/field-list-keyboard-navigation.js index 5e932e4c956244f0b8f429debdabeb28677b43ef..781c3646ee51919a5473d8b59cff7d610d50f5db 100644 --- a/core/misc/field-list-keyboard-navigation.js +++ b/core/misc/field-list-keyboard-navigation.js @@ -15,8 +15,7 @@ attach() { once( 'keyboardNavigation', - 'input[type="text"], input[type="number"]', - document.querySelector('[data-field-list-table]'), + '[data-field-list-table] :is(input[type="text"], input[type="number"])', ).forEach((element) => element.addEventListener('keypress', (event) => { if (event.key !== 'Enter') { diff --git a/core/modules/comment/tests/src/Functional/CommentFieldsTest.php b/core/modules/comment/tests/src/Functional/CommentFieldsTest.php index b227e6cf22c84f907354a80aad1b2827888c5a41..2d0ff7db9af9aa187da402eef0612feac7f67c89 100644 --- a/core/modules/comment/tests/src/Functional/CommentFieldsTest.php +++ b/core/modules/comment/tests/src/Functional/CommentFieldsTest.php @@ -151,15 +151,12 @@ public function testCommentFieldCreate(): void { $this->drupalLogin($user); // Create comment field in account settings. - $edit = [ - 'new_storage_type' => 'comment', - ]; - $this->drupalGet('admin/config/people/accounts/fields/add-field'); - $this->submitForm($edit, 'Continue'); $edit = [ 'label' => 'User comment', 'field_name' => 'user_comment', ]; + $this->drupalGet('admin/config/people/accounts/fields/add-field'); + $this->clickLink('Comments'); $this->submitForm($edit, 'Continue'); // Try to save the comment field without selecting a comment type. @@ -192,7 +189,7 @@ public function testCommentFieldCreate(): void { 'settings[per_page]' => 0, ]; $this->drupalGet('admin/config/people/accounts/add-field/user/field_user_comment'); - $this->submitForm($edit, 'Save settings'); + $this->submitForm($edit, 'Save'); $this->assertSession()->statusMessageContains('Saved User comment configuration.', 'status'); } diff --git a/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php b/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php index 2a11aeb3d1e8e8c4497569df98f78f60c5f65a06..203a0fff42566ce4894be4cd469aae0c02f8f885 100644 --- a/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php +++ b/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php @@ -505,9 +505,9 @@ public function testsNonIntegerIdEntities(): void { // Visit the Field UI field add page. $this->drupalGet('entity_test_string_id/structure/entity_test/fields/add-field'); // Ensure field isn't shown for string IDs. - $this->assertSession()->elementNotExists('css', "[name='new_storage_type'][value='comment']"); + $this->assertSession()->elementNotExists('xpath', "//a//span[text()='Comments']"); // Ensure a core field type shown. - $this->assertSession()->elementExists('css', "[name='new_storage_type'][value='boolean']"); + $this->assertSession()->elementExists('xpath', "//a//span[text()='Boolean']"); // Attempt to add a comment-type referencing this entity-type. $this->drupalGet('admin/structure/comment/types/add'); @@ -522,9 +522,9 @@ public function testsNonIntegerIdEntities(): void { // Visit the Field UI field add page. $this->drupalGet('entity_test_no_id/structure/entity_test/fields/add-field'); // Ensure field isn't shown for empty IDs. - $this->assertSession()->elementNotExists('css', "[name='new_storage_type'][value='comment']"); + $this->assertSession()->elementNotExists('xpath', "//a//span[text()='Comments']"); // Ensure a core field type shown. - $this->assertSession()->elementExists('css', "[name='new_storage_type'][value='boolean']"); + $this->assertSession()->elementExists('xpath', "//a//span[text()='Boolean']"); } /** diff --git a/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceAdminTest.php b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceAdminTest.php index 2570f2a73e97130489d60fc33d0364d53ad94b03..d8496e30e99bcd9e35af7e6f6b5cf0dbb762c170 100644 --- a/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceAdminTest.php +++ b/core/modules/field/tests/src/Functional/EntityReference/EntityReferenceAdminTest.php @@ -133,7 +133,7 @@ public function testFieldAdminHandler(): void { 'required' => FALSE, 'settings[handler_settings][view][view_and_display]' => 'node_test_view:entity_reference_1', ]; - $this->submitForm($edit, 'Save settings'); + $this->submitForm($edit, 'Save'); $this->assertSession()->statusMessageContains("Saved Test Entity Reference Field configuration.", MessengerInterface::TYPE_STATUS); $this->assertFieldExistsOnOverview('Test Entity Reference Field'); diff --git a/core/modules/field/tests/src/FunctionalJavascript/Boolean/BooleanFormatterSettingsTest.php b/core/modules/field/tests/src/FunctionalJavascript/Boolean/BooleanFormatterSettingsTest.php index eff590f1639a698a226d733ff8f69909ca798bfd..d85495c8e84ed079ab4f9a2c41a20626b0f78919 100644 --- a/core/modules/field/tests/src/FunctionalJavascript/Boolean/BooleanFormatterSettingsTest.php +++ b/core/modules/field/tests/src/FunctionalJavascript/Boolean/BooleanFormatterSettingsTest.php @@ -117,6 +117,7 @@ public function testBooleanFormatterSettings(): void { 'settings[off_label]' => $values[1], ], 'Save settings'); + $assert_session->waitForText('Saved ' . $this->fieldName . ' configuration.'); // Open the Manage Display page and trigger the field settings form. $this->drupalGet('admin/structure/types/manage/' . $this->bundle . '/display'); $this->getSession()->getPage()->pressButton($this->fieldName . '_settings_edit'); diff --git a/core/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php b/core/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php index 85f84b0ad41d62ba3aa3d331ae9129c240ce82b6..129f28576d612b8bf67ce84c8440b9159341cff3 100644 --- a/core/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php +++ b/core/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php @@ -33,6 +33,7 @@ class EntityReferenceAdminTest extends WebDriverTestBase { */ protected static $modules = [ 'node', + 'block', 'field_ui', 'path', 'taxonomy', @@ -65,6 +66,7 @@ class EntityReferenceAdminTest extends WebDriverTestBase { protected function setUp(): void { parent::setUp(); $this->drupalPlaceBlock('system_breadcrumb_block'); + $this->drupalPlaceBlock('local_actions_block'); // Create a content type, with underscores. $type_name = $this->randomMachineName(8) . '_test'; @@ -121,16 +123,18 @@ public function testFieldAdminHandler(): void { $assert_session = $this->assertSession(); // First step: 'Add new field' on the 'Manage fields' page. - $this->drupalGet($bundle_path . '/fields/add-field'); - + $this->drupalGet($bundle_path . '/fields'); + $this->clickLink('Create a new field'); + $this->assertSession()->assertWaitOnAjaxRequest(); // Check if the commonly referenced entity types appear in the list. - $page->find('css', "[name='new_storage_type'][value='reference']")->getParent()->click(); - $page->pressButton('Continue'); - $assert_session->pageTextContains('Choose an option below'); - $this->assertSession()->elementExists('css', "[name='group_field_options_wrapper'][value='field_ui:entity_reference:node']"); - $this->assertSession()->elementExists('css', "[name='group_field_options_wrapper'][value='field_ui:entity_reference:user']"); + $this->clickLink('Reference'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->assertTrue($assert_session->waitForText('Choose a field type')); + $this->assertSession()->elementExists('css', "[name='field_options_wrapper'][value='field_ui:entity_reference:node']"); + $this->assertSession()->elementExists('css', "[name='field_options_wrapper'][value='field_ui:entity_reference:user']"); - $page->pressButton('Back'); + $this->assertSession()->buttonExists('Change field type')->press(); + $this->assertSession()->assertWaitOnAjaxRequest(); $this->fieldUIAddNewFieldJS(NULL, 'test', 'Test', 'entity_reference', FALSE); // Node should be selected by default. @@ -254,7 +258,13 @@ public function testFieldAdminHandler(): void { // Third step: confirm. $page->findField('settings[handler_settings][target_bundles][' . $this->targetType . ']')->setValue($this->targetType); $assert_session->assertWaitOnAjaxRequest(); - $this->submitForm(['required' => '1'], 'Save settings'); + // @todo remove after https://www.drupal.org/i/3395590 has been fixed. + $this->getSession()->resizeWindow(1200, 1500); + usleep(5000); + + $page->find('css', '.ui-dialog-buttonset')->pressButton('Save'); + + $this->assertTrue($assert_session->waitForText('Saved Test configuration.')); // Check that the field appears in the overview form. $this->assertSession()->elementTextContains('xpath', '//table[@id="field-overview"]//tr[@id="field-test"]/td[1]', "Test"); @@ -262,13 +272,19 @@ public function testFieldAdminHandler(): void { // Check that the field settings form can be submitted again, even when the // field is required. // The first 'Edit' link is for the Body field. + $this->drupalGet($bundle_path . '/fields'); $this->clickLink('Edit', 1); - $this->submitForm([], 'Save settings'); + + $assert_session->assertExpectedAjaxRequest(1); + $this->assertNotNull($button_set = $assert_session->waitForElement('css', '.ui-dialog-buttonset')); + $button_set->pressButton('Save settings'); + $this->assertTrue($assert_session->waitForText('Saved Test configuration.')); // Switch the target type to 'taxonomy_term' and check that the settings // specific to its selection handler are displayed. $field_name = 'node.' . $this->type . '.field_test'; $this->drupalGet($bundle_path . '/fields/' . $field_name); + $this->assertTrue($assert_session->waitForText('These settings apply to the Test field everywhere it is used.')); $page->findField('field_storage[subform][settings][target_type]')->setValue('taxonomy_term'); $this->assertSession()->assertWaitOnAjaxRequest(); $this->assertSession()->fieldExists('settings[handler_settings][auto_create]'); @@ -310,7 +326,7 @@ public function testFieldAdminHandler(): void { $this->submitForm([], 'Save settings'); // If no eligible view is available we should see a message. - $assert_session->pageTextContains('The views entity selection mode requires a view.'); + $this->assertTrue($assert_session->waitForText('The views entity selection mode requires a view.')); // Enable the entity_reference_test module which creates an eligible view. $this->container->get('module_installer') @@ -322,21 +338,20 @@ public function testFieldAdminHandler(): void { ->waitForField('settings[handler_settings][view][view_and_display]') ->setValue('test_entity_reference:entity_reference_1'); $this->submitForm([], 'Save settings'); - $assert_session->pageTextContains('Saved Test configuration.'); + $this->assertTrue($assert_session->waitForText('Saved Test configuration.')); // Switch the target type to 'entity_test'. $this->drupalGet($bundle_path . '/fields/' . $field_name); $page->findField('field_storage[subform][settings][target_type]')->setValue('entity_test'); - $assert_session->assertWaitOnAjaxRequest(); + $assert_session->assertExpectedAjaxRequest(1); $page->findField('settings[handler]')->setValue('views'); + $assert_session->waitForField('settings[handler_settings][view][view_and_display]'); $page ->findField('settings[handler_settings][view][view_and_display]') ->selectOption('test_entity_reference_entity_test:entity_reference_1'); - $edit = [ - 'required' => FALSE, - ]; - $this->submitForm($edit, 'Save settings'); - $assert_session->pageTextContains('Saved Test configuration.'); + $this->assertSession()->fieldExists('required')->check(); + $this->submitForm([], 'Save settings'); + $this->assertTrue($assert_session->waitForText('Saved Test configuration.')); } /** diff --git a/core/modules/field_ui/css/field_ui_add_field.module.css b/core/modules/field_ui/css/field_ui_add_field.module.css index d10fd5cd77f18b7496cc4b01d0a83a9ea23bd9b9..d12e6cc8a302a65fa6ff751ae028159f71ed47b5 100644 --- a/core/modules/field_ui/css/field_ui_add_field.module.css +++ b/core/modules/field_ui/css/field_ui_add_field.module.css @@ -9,7 +9,7 @@ * @file field_ui_add_field.module.css */ -.field-ui-field-storage-add-form { +.add-field-container { --thumb-size: 4.5rem; --color-focus: #26a769; --color-gray: #232429; @@ -47,23 +47,29 @@ } } -@media (min-width: 87.5rem) { - .add-field-container { - grid-template-columns: repeat(4, 1fr); - } -} - .field-option { display: grid; grid-template-columns: var(--thumb-size) auto; align-items: center; padding: 0.25rem; padding-inline-end: 0.75rem; + -webkit-text-decoration: none; + text-decoration: none; + color: unset; border: 1px solid #dedfe4; gap: 0.75rem; border-radius: 0.25rem; } +.field-option:hover { + color: unset; +} + +.group-field-options { + display: table; + min-width: 100%; +} + .field-option__item { display: grid; grid-template-rows: auto 2fr; @@ -106,6 +112,10 @@ margin-inline: 0; } +.subfield-option .item-list { + padding-left: 1.4rem; +} + .field-option, .subfield-option { cursor: pointer; diff --git a/core/modules/field_ui/css/field_ui_add_field.module.pcss.css b/core/modules/field_ui/css/field_ui_add_field.module.pcss.css index 54d5be8aa1eefb9c76438695d4178064e5800075..b136e0f4b3cc9a176db5482dbb98bd12c716559b 100644 --- a/core/modules/field_ui/css/field_ui_add_field.module.pcss.css +++ b/core/modules/field_ui/css/field_ui_add_field.module.pcss.css @@ -2,7 +2,7 @@ * @file field_ui_add_field.module.css */ -.field-ui-field-storage-add-form { +.add-field-container { --thumb-size: 72px; --color-focus: #26a769; --color-gray: #232429; @@ -35,10 +35,6 @@ @media (min-width: 75rem) { grid-template-columns: repeat(3, 1fr); } - - @media (min-width: 87.5rem) { - grid-template-columns: repeat(4, 1fr); - } } .field-option { @@ -47,9 +43,19 @@ align-items: center; padding: 0.25rem; padding-inline-end: 0.75rem; + text-decoration: none; + color: unset; border: 1px solid #dedfe4; gap: 0.75rem; border-radius: 4px; + &:hover { + color: unset; + } +} + +.group-field-options { + display: table; + min-width: 100%; } .field-option__item { @@ -92,6 +98,9 @@ .item-list ul { margin-inline: 0; } + .item-list { + padding-left: 1.4rem; + } } .field-option, diff --git a/core/modules/field_ui/src/Controller/FieldStorageAddController.php b/core/modules/field_ui/src/Controller/FieldStorageAddController.php new file mode 100644 index 0000000000000000000000000000000000000000..35f85832bbc7b9ecf6815897f1e2ee0a4c68848b --- /dev/null +++ b/core/modules/field_ui/src/Controller/FieldStorageAddController.php @@ -0,0 +1,208 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\field_ui\Controller; + +use Drupal\Component\Serialization\Json; +use Drupal\Component\Utility\Html; +use Drupal\Component\Utility\SortArray; +use Drupal\Core\Ajax\AjaxHelperTrait; +use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Field\FallbackFieldTypeCategory; +use Drupal\Core\Field\FieldTypeCategoryManagerInterface; +use Drupal\Core\Field\FieldTypePluginManagerInterface; +use Drupal\Core\TempStore\PrivateTempStore; +use Drupal\Core\Url; +use Drupal\field_ui\FieldUI; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Controller for building the field type links. + * + * @internal + */ +final class FieldStorageAddController extends ControllerBase { + use AjaxHelperTrait; + + /** + * The name of the entity type. + * + * @var string + */ + protected $entityTypeId; + + /** + * The entity bundle. + * + * @var string + */ + protected $bundle; + + /** + * Constructs a new FieldStorageAddController. + */ + public function __construct( + protected FieldTypePluginManagerInterface $fieldTypePluginManager, + protected FieldTypeCategoryManagerInterface $fieldTypeCategoryManager, + protected PrivateTempStore $tempStore, + ) {} + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('plugin.manager.field.field_type'), + $container->get('plugin.manager.field.field_type_category'), + $container->get('tempstore.private')->get('field_ui'), + ); + } + + /** + * Deletes stored field data and builds the field selection links. + * + * @param string $entity_type_id + * The name of the entity type. + * @param string $bundle + * The entity bundle. + * @param string $field_name + * The field name. + * + * @return array + * The field selection links. + */ + public function resetField(string $entity_type_id, string $bundle, string $field_name) { + // Delete stored field data in case user changes field type. + $this->tempStore->delete("$entity_type_id:$field_name"); + return $this->getFieldSelectionLinks($entity_type_id, $bundle); + } + + /** + * Builds the field selection links. + * + * @param string $entity_type_id + * The name of the entity type. + * @param string $bundle + * The entity bundle. + * + * @return array + * The field selection links. + */ + public function getFieldSelectionLinks(string $entity_type_id, string $bundle) { + $build = []; + $this->entityTypeId = $entity_type_id; + $this->bundle = $bundle; + $ui_definitions = $this->fieldTypePluginManager->getEntityTypeUiDefinitions($entity_type_id); + $field_type_options = $unique_definitions = []; + $grouped_definitions = $this->fieldTypePluginManager->getGroupedDefinitions($ui_definitions, 'label', 'id'); + $category_definitions = $this->fieldTypeCategoryManager->getDefinitions(); + // Invoke a hook to get category properties. + foreach ($grouped_definitions as $category => $field_types) { + foreach ($field_types as $name => $field_type) { + $unique_definitions[$category][$name] = ['unique_identifier' => $name] + $field_type; + if ($this->fieldTypeCategoryManager->hasDefinition($category)) { + $category_plugin = $this->fieldTypeCategoryManager->createInstance($category, $unique_definitions[$category][$name], $category_definitions[$category]); + $field_type_options[$category_plugin->getPluginId()] = ['unique_identifier' => $name] + $field_type; + } + else { + $field_type_options[(string) $field_type['label']] = ['unique_identifier' => $name] + $field_type; + } + } + } + $build['add-label'] = [ + '#type' => 'label', + '#title' => $this->t('Choose a type of field'), + '#title_display' => 'before', + '#required' => TRUE, + ]; + + $build['add'] = [ + '#type' => 'container', + '#attributes' => [ + 'class' => 'add-field-container', + ], + ]; + + $field_type_options_radios = []; + foreach ($field_type_options as $id => $field_type) { + /** @var \Drupal\Core\Field\FieldTypeCategoryInterface $category_info */ + $category_info = $this->fieldTypeCategoryManager->createInstance($field_type['category'], $field_type); + $entity_type = $this->entityTypeManager()->getDefinition($this->entityTypeId); + $display_as_group = !($category_info instanceof FallbackFieldTypeCategory); + $route_parameters = [ + 'entity_type' => $this->entityTypeId, + 'bundle' => $this->bundle, + 'display_as_group' => $display_as_group ? 'true' : 'false', + 'selected_field_type' => $category_info->getPluginId(), + ] + FieldUI::getRouteBundleParameter($entity_type, $this->bundle); + $cleaned_class_name = Html::getClass($field_type['unique_identifier']); + $field_type_options_radios[$id] = [ + '#type' => 'html_tag', + '#tag' => 'a', + '#attributes' => [ + 'class' => ['field-option', 'use-ajax'], + 'role' => 'button', + 'tabindex' => '0', + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => 1100, + 'title' => $this->t('Add field: @type', ['@type' => $category_info->getLabel()]), + ]), + 'href' => Url::fromRoute("field_ui.field_storage_config_add_sub_{$this->entityTypeId}", $route_parameters)->toString(), + ], + '#weight' => $category_info->getWeight(), + 'thumb' => [ + '#type' => 'container', + '#attributes' => [ + 'class' => ['field-option__thumb'], + ], + 'icon' => [ + '#type' => 'container', + '#attributes' => [ + 'class' => ['field-option__icon', $display_as_group ? + "field-icon-{$field_type['category']}" : "field-icon-$cleaned_class_name", + ], + ], + ], + ], + // Store some data we later need. + '#data' => [ + '#group_display' => $display_as_group, + ], + 'words' => [ + '#type' => 'container', + '#attributes' => [ + 'class' => ['field-option__words'], + ], + 'label' => [ + '#attributes' => [ + 'class' => ['field-option__label'], + ], + '#type' => 'html_tag', + '#tag' => 'span', + '#value' => $category_info->getLabel(), + ], + 'description' => [ + '#type' => 'container', + '#attributes' => [ + 'class' => ['field-option__description'], + ], + '#markup' => $category_info->getDescription(), + ], + ], + ]; + + if ($libraries = $category_info->getLibraries()) { + $field_type_options_radios[$id]['#attached']['library'] = $libraries; + } + } + uasort($field_type_options_radios, [SortArray::class, 'sortByWeightProperty']); + $build['add']['new_storage_type'] = $field_type_options_radios; + $build['#attached']['library'][] = 'field_ui/drupal.field_ui'; + $build['#attached']['library'][] = 'field_ui/drupal.field_ui.manage_fields'; + $build['#attached']['library'][] = 'core/drupal.dialog.ajax'; + return $build; + } + +} diff --git a/core/modules/field_ui/src/FieldConfigListBuilder.php b/core/modules/field_ui/src/FieldConfigListBuilder.php index 0e4fbfeb489c071bb47a679cca5e8e7b01f96580..92bf9a3ff1b6f7952fa3b4d9930c94fa5dc3e6a0 100644 --- a/core/modules/field_ui/src/FieldConfigListBuilder.php +++ b/core/modules/field_ui/src/FieldConfigListBuilder.php @@ -185,6 +185,11 @@ public function getDefaultOperations(EntityInterface $entity) { 'url' => $entity->toUrl("{$entity->getTargetEntityTypeId()}-field-edit-form"), 'attributes' => [ 'title' => $this->t('Edit field settings.'), + 'class' => ['use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => 1100, + ]), ], ]; } diff --git a/core/modules/field_ui/src/Form/FieldConfigEditForm.php b/core/modules/field_ui/src/Form/FieldConfigEditForm.php index 60c76fc583568bfa0256c7ac6465093d9dbf1590..04e6ebb92f8f37b29700137ae1418b58b5d68bcd 100644 --- a/core/modules/field_ui/src/Form/FieldConfigEditForm.php +++ b/core/modules/field_ui/src/Form/FieldConfigEditForm.php @@ -2,6 +2,10 @@ namespace Drupal\field_ui\Form; +use Drupal\Component\Serialization\Json; +use Drupal\Core\Ajax\AjaxResponse; +use Drupal\Core\Ajax\RedirectCommand; +use Drupal\Core\Ajax\ReplaceCommand; use Drupal\Core\Entity\EntityDisplayRepositoryInterface; use Drupal\Core\Entity\EntityForm; use Drupal\Core\Entity\EntityInterface; @@ -296,7 +300,31 @@ private function hasAnyRequired(array $element) { */ protected function actions(array $form, FormStateInterface $form_state) { $actions = parent::actions($form, $form_state); - $actions['submit']['#value'] = $this->t('Save settings'); + $actions['submit']['#value'] = $this->entity->isNew() ? $this->t('Save') : $this->t('Save settings'); + $actions['submit']['#ajax'] = [ + 'callback' => '::ajaxSubmit', + ]; + if ($this->entity->isNew()) { + $entity_type = $this->entity->getTargetEntityTypeId(); + $route_parameters = [ + 'field_name' => $this->entity->getName(), + 'entity_type' => $entity_type, + ] + FieldUI::getRouteBundleParameter($this->entityTypeManager->getDefinition($entity_type), $this->entity->getTargetBundle()); + $actions['back'] = [ + '#type' => 'link', + '#weight' => 1, + '#title' => $this->t('Change field type'), + '#limit_validation_errors' => [], + '#attributes' => [ + 'class' => ['button', 'use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => '1100', + ]), + ], + '#url' => Url::fromRoute("field_ui.field_storage_config_reset_add_$entity_type", $route_parameters), + ]; + } if (!$this->entity->isNew()) { $target_entity_type = $this->entityTypeManager->getDefinition($this->entity->getTargetEntityTypeId()); @@ -305,18 +333,17 @@ protected function actions(array $form, FormStateInterface $form_state) { ] + FieldUI::getRouteBundleParameter($target_entity_type, $this->entity->getTargetBundle()); $url = new Url('entity.field_config.' . $target_entity_type->id() . '_field_delete_form', $route_parameters); - if ($this->getRequest()->query->has('destination')) { - $query = $url->getOption('query'); - $query['destination'] = $this->getRequest()->query->get('destination'); - $url->setOption('query', $query); - } $actions['delete'] = [ '#type' => 'link', '#title' => $this->t('Delete'), '#url' => $url, '#access' => $this->entity->access('delete'), '#attributes' => [ - 'class' => ['button', 'button--danger'], + 'class' => ['button', 'button--danger', 'use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => '1100', + ]), ], ]; } @@ -324,11 +351,63 @@ protected function actions(array $form, FormStateInterface $form_state) { return $actions; } + /** + * Submit form #ajax callback. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * An AJAX response that display validation error messages or represents a + * successful submission. + * + * @see \Drupal\Core\Ajax\AjaxFormHelperTrait + */ + public function ajaxSubmit(array &$form, FormStateInterface $form_state): AjaxResponse { + if ($form_state->hasAnyErrors()) { + $form['status_messages'] = [ + '#type' => 'status_messages', + '#weight' => -1000, + ]; + $form['#sorted'] = FALSE; + $response = new AjaxResponse(); + $response->addCommand(new ReplaceCommand('#field-combined', $form)); + } + else { + $response = $this->successfulAjaxSubmit($form, $form_state); + } + return $response; + } + + /** + * Respond to a successful AJAX submission. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * An AJAX response. + */ + protected function successfulAjaxSubmit(array $form, FormStateInterface $form_state): AjaxResponse { + $response = new AjaxResponse(); + $response->addCommand(new RedirectCommand(FieldUI::getOverviewRouteInfo($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle())->toString())); + + return $response; + } + /** * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { parent::validateForm($form, $form_state); + // Additional validation to work when JS is disabled. + if (!$form_state->getValue('label')) { + $form_state->setErrorByName('label', $this->t('Label field is required.')); + } $field_storage_form = $this->entityTypeManager->getFormObject('field_storage_config', $this->operation); $field_storage_form->setEntity($this->entity->getFieldStorageDefinition()); diff --git a/core/modules/field_ui/src/Form/FieldStorageAddForm.php b/core/modules/field_ui/src/Form/FieldStorageAddForm.php index c6bfad7f3a95690f7e4b70f5f705fdf10438833a..c89f36efd0a59baea218cd5678d9e2c55010830c 100644 --- a/core/modules/field_ui/src/Form/FieldStorageAddForm.php +++ b/core/modules/field_ui/src/Form/FieldStorageAddForm.php @@ -2,28 +2,35 @@ namespace Drupal\field_ui\Form; +use Drupal\Component\Serialization\Json; use Drupal\Component\Utility\Html; use Drupal\Component\Utility\SortArray; +use Drupal\Core\Ajax\AjaxHelperTrait; +use Drupal\Core\Ajax\AjaxResponse; +use Drupal\Core\Ajax\OpenModalDialogWithUrl; +use Drupal\Core\Ajax\ReplaceCommand; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Field\FallbackFieldTypeCategory; use Drupal\Core\Field\FieldTypeCategoryManagerInterface; use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\TempStore\PrivateTempStore; +use Drupal\Core\Url; use Drupal\field\Entity\FieldStorageConfig; use Drupal\field_ui\FieldUI; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Provides a form for the "field storage" add page. + * Provides a form for the "field storage" add subform. * * @internal */ class FieldStorageAddForm extends FormBase { + use AjaxHelperTrait; + /** * The name of the entity type. * @@ -66,198 +73,48 @@ public function getFormId() { /** * {@inheritdoc} */ - public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL, $bundle = NULL) { + public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL, $bundle = NULL, $selected_field_type = NULL, $display_as_group = 'false') { + $display_as_group = str_contains($display_as_group, 'true'); if (!$form_state->get('entity_type_id')) { $form_state->set('entity_type_id', $entity_type_id); } if (!$form_state->get('bundle')) { $form_state->set('bundle', $bundle); } - $this->entityTypeId = $form_state->get('entity_type_id'); - $this->bundle = $form_state->get('bundle'); - - if (!$form_state->has('field_type_options') || !$form_state->has('unique_definitions')) { - $this->processFieldDefinitions($form_state); - } - - // Place the 'translatable' property as an explicit value so that contrib - // modules can form_alter() the value for newly created fields. By default - // we create field storage as translatable so it will be possible to enable - // translation at field level. - $form['translatable'] = [ - '#type' => 'value', - '#value' => TRUE, - ]; - - $form['actions'] = ['#type' => 'actions']; - $form['actions']['submit'] = [ - '#type' => 'submit', - '#value' => $this->t('Continue'), - '#button_type' => 'primary', - ]; - - $form['#attached']['library'] = [ - 'field_ui/drupal.field_ui', - 'field_ui/drupal.field_ui.manage_fields', - 'core/drupal.ajax', - ]; - - if ($form_state->hasValue('new_storage_type')) { - // A group is already selected. Show field types for that group. - $this->addFieldOptionsForGroup($form, $form_state); + if (!$form_state->get('field_type')) { + $form_state->set('field_type', $selected_field_type); } - else { - // Show options for groups and ungrouped field types. - $this->addGroupFieldOptions($form, $form_state); + if (!$form_state->get('display_as_group')) { + $form_state->set('display_as_group', $display_as_group); } + $this->entityTypeId = $form_state->get('entity_type_id'); + $this->bundle = $form_state->get('bundle'); - return $form; - } - - /** - * Save field type definitions and categories in the form state. - * - * Get all field type definitions and store each one twice: - * - field_type_options: each field type is indexed by its category plugin ID - * or its label. - * - unique_definitions: each field type is indexed by its category and name. - * - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - protected function processFieldDefinitions(FormStateInterface $form_state): void { - $field_type_options = $unique_definitions = []; + $unique_definitions = []; $grouped_definitions = $this->fieldTypePluginManager ->getGroupedDefinitions($this->fieldTypePluginManager->getEntityTypeUiDefinitions($this->entityTypeId), 'label', 'id'); - foreach ($grouped_definitions as $category => $field_types) { + if (array_key_exists($selected_field_type, $grouped_definitions)) { + $field_types = $grouped_definitions[$selected_field_type]; foreach ($field_types as $name => $field_type) { - $definition = ['unique_identifier' => $name] + $field_type; - $category_info = $this->fieldTypeCategoryManager - ->createInstance($field_type['category'], $definition); - $definition['display_as_group'] = !($category_info instanceof FallbackFieldTypeCategory); - $id = $this->fieldTypeCategoryManager->hasDefinition($category) - ? $category_info->getPluginId() - : (string) $field_type['label']; - $field_type_options[$id] = $definition; - $unique_definitions[$category][$name] = $definition; - } - } - - $form_state->set('field_type_options', $field_type_options); - $form_state->set('unique_definitions', $unique_definitions); - } - - /** - * Adds ungrouped field types and field type groups to the form. - * - * When a group is selected, the related fields are shown when the form is - * rebuilt. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - protected function addGroupFieldOptions(array &$form, FormStateInterface $form_state): void { - $field_type_options_radios = []; - foreach ($form_state->get('field_type_options') as $id => $field_type) { - /** @var \Drupal\Core\Field\FieldTypeCategoryInterface $category_info */ - $category_info = $this->fieldTypeCategoryManager - ->createInstance($field_type['category'], $field_type); - $display_as_group = $field_type['display_as_group']; - $cleaned_class_name = Html::getClass($field_type['unique_identifier']); - $field_type_options_radios[$id] = [ - '#type' => 'container', - '#attributes' => [ - 'class' => ['field-option', 'js-click-to-select'], - ], - '#weight' => $category_info->getWeight(), - 'thumb' => [ - '#type' => 'container', - '#attributes' => [ - 'class' => ['field-option__thumb'], - ], - 'icon' => [ - '#type' => 'container', - '#attributes' => [ - 'class' => [ - 'field-option__icon', - $display_as_group ? "field-icon-$field_type[category]" : "field-icon-$cleaned_class_name", - ], - ], - ], - ], - 'radio' => [ - '#type' => 'radio', - '#title' => $category_info->getLabel(), - '#parents' => ['new_storage_type'], - '#title_display' => 'before', - '#description_display' => 'before', - '#theme_wrappers' => ['form_element__new_storage_type'], - // If it is a category, set return value as the category label. - // Otherwise, set it as the field type id. - '#return_value' => $display_as_group ? $field_type['category'] : $field_type['unique_identifier'], - '#attributes' => [ - 'class' => ['field-option-radio'], - ], - '#description' => [ - '#type' => 'container', - '#attributes' => [ - 'class' => ['field-option__description'], - ], - '#markup' => $category_info->getDescription(), - ], - '#variant' => 'field-option', - ], - ]; - - if ($libraries = $category_info->getLibraries()) { - $field_type_options_radios[$id]['#attached']['library'] = $libraries; + $unique_definitions[$selected_field_type][$name] = ['unique_identifier' => $name] + $field_type; } } - uasort($field_type_options_radios, [SortArray::class, 'sortByWeightProperty']); - - $form['add-label'] = [ - '#type' => 'label', - '#title' => $this->t('Choose a type of field'), - '#required' => TRUE, - ]; - - $form['add'] = [ - '#type' => 'container', - '#attributes' => [ - 'class' => 'add-field-container', - ], - ]; - $form['add']['new_storage_type'] = $field_type_options_radios; + $entity_type = $this->entityTypeManager->getDefinition($this->entityTypeId); + $route_parameters_back = [] + FieldUI::getRouteBundleParameter($entity_type, $this->bundle); - $form['actions']['submit']['#validate'][] = '::validateGroupOrField'; - $form['actions']['submit']['#submit'][] = '::rebuildWithOptions'; - } + $form['actions'] = ['#type' => 'actions']; - /** - * Adds field types for the selected group to the form. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - protected function addFieldOptionsForGroup(array &$form, FormStateInterface $form_state): void { - // Field label and field_name. - $form['new_storage_wrapper'] = [ - '#type' => 'container', - '#attributes' => [ - 'class' => ['field-ui-new-storage-wrapper'], - ], - ]; - $form['new_storage_wrapper']['label'] = [ + $form['label'] = [ '#type' => 'textfield', '#title' => $this->t('Label'), '#size' => 30, + '#required' => TRUE, + '#maxlength' => 255, + '#weight' => -20, ]; - $field_prefix = $this->config('field_ui.settings')->get('field_prefix'); - $form['new_storage_wrapper']['field_name'] = [ + + $field_prefix = $this->configFactory->get('field_ui.settings')->get('field_prefix'); + $form['field_name'] = [ '#type' => 'machine_name', '#field_prefix' => $field_prefix, '#size' => 15, @@ -266,128 +123,147 @@ protected function addFieldOptionsForGroup(array &$form, FormStateInterface $for // setting. Maximum length is 32. '#maxlength' => FieldStorageConfig::NAME_MAX_LENGTH - strlen($field_prefix), '#machine_name' => [ - 'source' => ['new_storage_wrapper', 'label'], + 'source' => ['label'], 'exists' => [$this, 'fieldNameExists'], ], - '#required' => FALSE, - ]; - - $form['actions']['submit']['#validate'][] = '::validateFieldType'; - - $form['actions']['back'] = [ - '#type' => 'submit', - '#value' => $this->t('Back'), - '#submit' => ['::startOver'], - ]; - - $field_type_options = $form_state->get('field_type_options'); - $new_storage_type = $form_state->getValue('new_storage_type'); - $form['new_storage_type'] = [ - '#type' => 'value', - '#value' => $new_storage_type, + '#required' => TRUE, ]; - if (!isset($new_storage_type) || !$field_type_options[$new_storage_type]['display_as_group']) { - return; - } - - // Create a wrapper for all the field options to be provided. - $form['group_field_options_wrapper'] = [ - '#prefix' => '<div id="group-field-options-wrapper" class="group-field-options-wrapper">', + $form['field_options_wrapper'] = [ + '#prefix' => '<div class="field-options-wrapper">', '#suffix' => '</div>', ]; - $form['group_field_options_wrapper']['label'] = [ - '#type' => 'label', - '#title' => $this->t('Choose an option below'), - '#required' => TRUE, - ]; - $form['group_field_options_wrapper']['fields'] = [ - '#type' => 'container', - '#attributes' => [ - 'class' => ['group-field-options'], - ], - ]; - $unique_definitions = $form_state->get('unique_definitions')[$new_storage_type] ?? []; - $group_field_options = []; - foreach ($unique_definitions as $option) { - $identifier = $option['unique_identifier']; - // If the field type plugin's annotation defines description as an - // array, render it as an item_list. - $description = !is_array($option['description']) ? $option['description'] : [ - '#theme' => 'item_list', - '#items' => $option['description'], - ]; - $radio_element = [ - '#type' => 'radio', - '#theme_wrappers' => ['form_element__new_storage_type'], - '#title' => $option['label'], - '#description' => $description, - '#id' => $identifier, - '#weight' => $option['weight'], - '#parents' => ['group_field_options_wrapper'], + // Set the selected field to the form state by checking + // the checked attribute. + if (isset($selected_field_type)) { + if ($display_as_group) { + $form['field_options_wrapper']['label'] = [ + '#type' => 'label', + '#title' => $this->t('Choose a field type'), + '#required' => TRUE, + ]; + $form['field_options_wrapper']['fields'] = [ + '#type' => 'container', + '#attributes' => [ + 'class' => ['group-field-options'], + ], + ]; + foreach ($unique_definitions[$selected_field_type] as $option_key => $option) { + $description = !is_array($option['description']) ? $option['description'] : [ + '#theme' => 'item_list', + '#items' => $option['description'], + ]; + $radio_element = [ + '#type' => 'radio', + '#theme_wrappers' => ['form_element__new_storage_type'], + '#title' => $option['label'], + '#description' => $description, + '#id' => Html::getClass($option['unique_identifier']), + '#weight' => $option['weight'], + '#parents' => ['field_options_wrapper'], + '#attributes' => [ + 'class' => ['field-option-radio'], + 'data-once' => 'field-click-to-select', + 'checked' => $this->getRequest()->request->get('field_options_wrapper') !== NULL && $this->getRequest()->request->get('field_options_wrapper') == $option_key, + ], + '#wrapper_attributes' => [ + 'class' => ['js-click-to-select', 'subfield-option'], + ], + '#variant' => 'field-suboption', + ]; + $radio_element['#return_value'] = $option['unique_identifier']; + if ((string) $option['unique_identifier'] === 'entity_reference') { + $radio_element['#title'] = 'Other'; + $radio_element['#weight'] = 10; + } + $group_field_options[$option['unique_identifier']] = $radio_element; + } + uasort($group_field_options, [SortArray::class, 'sortByWeightProperty']); + $form['field_options_wrapper']['fields'] += $group_field_options; + } + + $form['actions']['previous'] = [ + '#type' => 'link', + '#title' => $this->t('Change field type'), + '#url' => Url::fromRoute("field_ui.field_storage_config_add_$entity_type_id", $route_parameters_back), '#attributes' => [ - 'class' => ['field-option-radio'], - 'data-once' => 'field-click-to-select', + 'class' => ['button', 'use-ajax'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => '1100', + ]), ], - '#wrapper_attributes' => [ - 'class' => ['js-click-to-select', 'subfield-option'], + ]; + + $form['actions']['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Continue'), + '#submit' => ['::submitForm'], + '#attributes' => [ + 'class' => ['button', 'button--primary'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => '1100', + ]), ], - '#variant' => 'field-suboption', - '#return_value' => $identifier, ]; - if ($identifier === 'entity_reference') { - $radio_element['#title'] = 'Other'; - $radio_element['#weight'] = 10; + if ($this->isAjax()) { + $form['actions']['submit']['#ajax']['callback'] = '::ajaxSubmit'; } - $group_field_options[$identifier] = $radio_element; } - uasort($group_field_options, [SortArray::class, 'sortByWeightProperty']); - $form['group_field_options_wrapper']['fields'] += $group_field_options; - } - /** - * Validates the first step of the form. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. - */ - public function validateGroupOrField(array &$form, FormStateInterface $form_state) { - if (!$form_state->getValue('new_storage_type')) { - $form_state->setErrorByName('add', $this->t('You need to select a field type.')); - } + // Place the 'translatable' property as an explicit value so that contrib + // modules can form_alter() the value for newly created fields. By default, + // we create field storage as translatable, so it will be possible to enable + // translation at field level. + $form['translatable'] = [ + '#type' => 'value', + '#value' => TRUE, + ]; + + $form['#prefix'] = '<div id="field-storage-subfield">'; + $form['#suffix'] = '</div>'; + + $form['#attached']['library'] = [ + 'field_ui/drupal.field_ui', + 'field_ui/drupal.field_ui.manage_fields', + 'core/drupal.dialog.ajax', + ]; + return $form; } /** - * Validates the second step (field storage selection and label) of the form. - * - * @param array $form - * An associative array containing the structure of the form. - * @param \Drupal\Core\Form\FormStateInterface $form_state - * The current state of the form. + * {@inheritdoc} */ - public function validateFieldType(array $form, FormStateInterface $form_state) { - // Missing label. + public function validateForm(array &$form, FormStateInterface $form_state) { + // Missing subtype. + if (!$form_state->getValue('field_options_wrapper') && isset($form['field_options_wrapper']['fields'])) { + $form_state->setErrorByName('field_options_wrapper', $this->t('You need to select a field type.')); + } + // Additional validation to work when JS is disabled. if (!$form_state->getValue('label')) { - $form_state->setErrorByName('label', $this->t('Add new field: you need to provide a label.')); + $form_state->setErrorByName('label', $this->t('Label field is required.')); } - // Missing field name. if (!$form_state->getValue('field_name')) { - $form_state->setErrorByName('field_name', $this->t('Add new field: you need to provide a machine name for the field.')); + $form_state->setErrorByName('label', $this->t('Machine-readable name field is required.')); } // Field name validation. else { $field_name = $form_state->getValue('field_name'); // Add the field prefix. - $field_name = $this->configFactory->get('field_ui.settings')->get('field_prefix') . $field_name; - $form_state->setValueForElement($form['new_storage_wrapper']['field_name'], $field_name); - } - - if (isset($form['group_field_options_wrapper']['fields']) && !$form_state->getValue('group_field_options_wrapper')) { - $form_state->setErrorByName('group_field_options_wrapper', $this->t('You need to choose an option.')); + $field_name = $this->config('field_ui.settings')->get('field_prefix') . $field_name; + $form_state->setValueForElement($form['field_name'], $field_name); + // Set the temp store here, so we can actually see the error on the modal. + $field_storage_type = $form_state->getValue('field_options_wrapper') ?? $form_state->get('field_type'); + $this->setTempStore($this->entityTypeId, $field_storage_type, $this->bundle, $form_state->getValue('label'), $form_state->getValue('field_name'), $form_state->getValue('translatable')); + if (!empty($this->messenger()->messagesByType('error'))) { + $form_state->setErrorByName('drupal-modal', $this->t('There was a problem creating field @label: @message', ['@label' => $form_state->getValue('label'), '@message' => explode(':', $this->messenger()->messagesByType('error')[0])[1]])); + // We need to clear out the messenger so that we just see the message + // on the modal and not on the page when it closes. + $this->messenger()->deleteAll(); + } } } @@ -395,80 +271,73 @@ public function validateFieldType(array $form, FormStateInterface $form_state) { * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { - $values = $form_state->getValues(); - $entity_type = $this->entityTypeManager->getDefinition($this->entityTypeId); - - $field_storage_type = $values['group_field_options_wrapper'] ?? $values['new_storage_type']; - $field_name = $values['field_name']; - $field_values = [ - 'entity_type' => $this->entityTypeId, - 'bundle' => $this->bundle, - ]; - - // Check if we're dealing with a preconfigured field. - if (str_starts_with($field_storage_type, 'field_ui:')) { - [, $field_type, $preset_key] = explode(':', $field_storage_type, 3); - $default_options = $this->getNewFieldDefaults($field_type, $preset_key); - } - else { - $field_type = $field_storage_type; - $default_options = []; - } - $field_values += [ - ...$default_options['field_config'] ?? [], - 'field_name' => $field_name, - 'label' => $values['label'], - // Field translatability should be explicitly enabled by the users. - 'translatable' => FALSE, - ]; + $form_state->setRedirectUrl($this->getRedirectUrl($form_state->getValue('field_name'))); + } - $field_storage_values = [ - ...$default_options['field_storage_config'] ?? [], + /** + * Gets the redirect URL. + * + * @param string $field_name + * The field name. + * + * @return \Drupal\Core\Url + * The URL to redirect to. + * + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + */ + private function getRedirectUrl(string $field_name): Url { + $route_parameters = [ 'field_name' => $field_name, - 'type' => $field_type, 'entity_type' => $this->entityTypeId, - 'translatable' => $values['translatable'], - ]; + ] + FieldUI::getRouteBundleParameter($this->entityTypeManager->getDefinition($this->entityTypeId), $this->bundle); + return Url::fromRoute("field_ui.field_add_{$this->entityTypeId}", $route_parameters); + } - try { - $field_storage_entity = $this->entityTypeManager - ->getStorage('field_storage_config') - ->create($field_storage_values); + /** + * Submit form #ajax callback. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * An AJAX response that display validation error messages or represents a + * successful submission. + * + * @see \Drupal\Core\Ajax\AjaxFormHelperTrait + */ + public function ajaxSubmit(array &$form, FormStateInterface $form_state): AjaxResponse { + if ($form_state->hasAnyErrors()) { + $form['status_messages'] = [ + '#type' => 'status_messages', + '#weight' => -1000, + ]; + $form['#sorted'] = FALSE; + $response = new AjaxResponse(); + $response->addCommand(new ReplaceCommand('#field-storage-subfield', $form)); } - catch (\Exception $e) { - $this->messenger()->addError($this->t('There was a problem creating field %label: @message', ['%label' => $values['label'], '@message' => $e->getMessage()])); - return; + else { + $response = $this->successfulAjaxSubmit($form, $form_state); } + return $response; + } - // Save field and field storage values in tempstore. - $this->tempStore->set($this->entityTypeId . ':' . $field_name, [ - 'field_storage' => $field_storage_entity, - 'field_config_values' => $field_values, - 'default_options' => $default_options, - ]); - - // Configure next steps in the multi-part form. - $destinations = []; - $route_parameters = [ - 'entity_type' => $this->entityTypeId, - 'field_name' => $field_name, - ] + FieldUI::getRouteBundleParameter($entity_type, $this->bundle); - $destinations[] = [ - 'route_name' => "field_ui.field_add_{$this->entityTypeId}", - 'route_parameters' => $route_parameters, - ]; - $destinations[] = [ - 'route_name' => "entity.{$this->entityTypeId}.field_ui_fields", - 'route_parameters' => $route_parameters, - ]; - $destination = $this->getDestinationArray(); - $destinations[] = $destination['destination']; - $form_state->setRedirectUrl( - FieldUI::getNextDestination($destinations) - ); - - // Store new field information for any additional submit handlers. - $form_state->set(['fields_added', '_add_new_field'], $field_name); + /** + * Respond to a successful AJAX submission. + * + * @param array $form + * An associative array containing the structure of the form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\Core\Ajax\AjaxResponse + * An AJAX response. + */ + protected function successfulAjaxSubmit(array $form, FormStateInterface $form_state): AjaxResponse { + $response = new AjaxResponse(); + $response->addCommand(new OpenModalDialogWithUrl($this->getRedirectUrl($form_state->getValue('field_name'))->toString(), [])); + return $response; } /** @@ -526,6 +395,67 @@ protected function getNewFieldDefaults(string $field_name, string $preset_key): return $default_options; } + /** + * Store field information in temp store in order to build the edit form. + * + * @param string $entity_type + * The name of the entity type. + * @param string $field_storage_type + * The machine name of the field storage. + * @param string $bundle + * The entity bundle. + * @param string $field_label + * The label of the field. + * @param string $field_machine_name + * The machine name of the field. + * @param bool $translatable + * TRUE if the field is translatable. + */ + public function setTempStore(string $entity_type, string $field_storage_type, string $bundle, string $field_label, string $field_machine_name, bool $translatable): void { + $field_values = [ + 'entity_type' => $entity_type, + 'bundle' => $bundle, + ]; + $default_options = []; + // Check if we're dealing with a preconfigured field. + if (strpos($field_storage_type, 'field_ui:') === 0) { + [, $field_type, $preset_key] = explode(':', $field_storage_type, 3); + $default_options = $this->getNewFieldDefaults($field_type, $preset_key); + } + else { + $field_type = $field_storage_type; + } + $field_values += [ + ...$default_options['field_config'] ?? [], + 'field_name' => $field_machine_name, + 'label' => $field_label, + // Field translatability should be explicitly enabled by the users. + 'translatable' => FALSE, + ]; + + $field_storage_values = [ + ...$default_options['field_storage_config'] ?? [], + 'field_name' => $field_machine_name, + 'type' => $field_type, + 'entity_type' => $entity_type, + 'translatable' => $translatable, + ]; + try { + $field_storage_entity = $this->entityTypeManager->getStorage('field_storage_config')->create($field_storage_values); + } + catch (\Exception $e) { + $this->messenger()->addError($this->t('There was a problem creating field :@message', ['@message' => $e->getMessage()])); + return; + } + + // Save field and field storage values in tempstore. + $this->tempStore->set($entity_type . ':' . $field_machine_name, [ + 'field_storage' => $field_storage_entity, + 'field_config_values' => $field_values, + 'default_options' => $default_options, + ]); + } + /** * Checks if a field machine name is taken. * diff --git a/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php index a30d30d09c31b4920ba6f9a3621cfaf9e69a1313..f65afced00f31c5e48f3dfde77290d887c86cac6 100644 --- a/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php +++ b/core/modules/field_ui/src/Plugin/Derivative/FieldUiLocalAction.php @@ -64,6 +64,15 @@ public function getDerivativeDefinitions($base_plugin_definition) { 'route_name' => "field_ui.field_storage_config_add_$entity_type_id", 'title' => $this->t('Create a new field'), 'appears_on' => ["entity.$entity_type_id.field_ui_fields"], + 'options' => [ + 'attributes' => [ + 'class' => ['use-ajax', 'button'], + 'data-dialog-type' => 'modal', + 'data-dialog-options' => Json::encode([ + 'width' => '1100', + ]), + ], + ], ]; $this->derivatives["field_storage_config_reuse_$entity_type_id"] = [ 'route_name' => "field_ui.field_storage_config_reuse_$entity_type_id", @@ -74,7 +83,7 @@ public function getDerivativeDefinitions($base_plugin_definition) { 'class' => ['use-ajax', 'button'], 'data-dialog-type' => 'modal', 'data-dialog-options' => Json::encode([ - 'width' => '85vw', + 'width' => '1100', ]), ], ], diff --git a/core/modules/field_ui/src/Routing/RouteSubscriber.php b/core/modules/field_ui/src/Routing/RouteSubscriber.php index 52968245e74ca76bc5265065fd4b908295f07c05..91890fb28395d604b4f3528e6cb6e327ca6531e6 100644 --- a/core/modules/field_ui/src/Routing/RouteSubscriber.php +++ b/core/modules/field_ui/src/Routing/RouteSubscriber.php @@ -6,6 +6,7 @@ use Drupal\Core\Routing\RouteSubscriberBase; use Drupal\Core\Routing\RoutingEvents; use Drupal\field_ui\Controller\FieldConfigAddController; +use Drupal\field_ui\Controller\FieldStorageAddController; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; @@ -91,10 +92,24 @@ protected function alterRoutes(RouteCollection $collection) { ); $collection->add("entity.{$entity_type_id}.field_ui_fields", $route); + $route = new Route( + "$path/fields/reset-add-field/{field_name}", + [ + '_controller' => FieldStorageAddController::class . '::resetField', + '_title' => 'Add field', + ] + $defaults, + [ + '_permission' => 'administer ' . $entity_type_id . ' fields', + '_csrf_token' => 'TRUE', + ], + $options + ); + $collection->add("field_ui.field_storage_config_reset_add_$entity_type_id", $route); + $route = new Route( "$path/fields/add-field", [ - '_form' => '\Drupal\field_ui\Form\FieldStorageAddForm', + '_controller' => FieldStorageAddController::class . '::getFieldSelectionLinks', '_title' => 'Add field', ] + $defaults, ['_permission' => 'administer ' . $entity_type_id . ' fields'], @@ -102,6 +117,17 @@ protected function alterRoutes(RouteCollection $collection) { ); $collection->add("field_ui.field_storage_config_add_$entity_type_id", $route); + $route = new Route( + "$path/fields/add-field/{selected_field_type}/{display_as_group}", + [ + '_form' => '\Drupal\field_ui\Form\FieldStorageAddForm', + '_title' => 'Add Sub-field', + ] + $defaults, + ['_permission' => 'administer ' . $entity_type_id . ' fields'], + $options + ); + $collection->add("field_ui.field_storage_config_add_sub_$entity_type_id", $route); + $route = new Route( "$path/add-field/{entity_type}/{field_name}", [ diff --git a/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTest.php b/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTest.php index d272c824e8e8955d57cce8e0e563c2fe9fc8233f..f5feb26f99b2484c2e999e3f7f6c391734c858ad 100644 --- a/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTest.php +++ b/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTest.php @@ -148,8 +148,8 @@ public function testLockedField(): void { public function testHiddenFields(): void { // Check that the field type is not available in the 'add new field' row. $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/add-field'); - $this->assertSession()->elementNotExists('css', "[name='new_storage_type'][value='hidden_test_field']"); - $this->assertSession()->elementExists('css', "[name='new_storage_type'][value='shape']"); + $this->assertSession()->elementNotExists('xpath', "//a//span[text()='Hidden from UI test field']"); + $this->assertSession()->elementExists('xpath', "//a//span[text()='Shape']"); // Create a field storage and a field programmatically. $field_name = 'hidden_test_field'; @@ -184,26 +184,26 @@ public function testHiddenFields(): void { // Check that non-configurable fields are not available. $field_types = \Drupal::service('plugin.manager.field.field_type')->getDefinitions(); - $this->drupalGet('admin/structure/types/manage/page/fields/add-field'); foreach ($field_types as $field_type => $definition) { + $this->drupalGet('admin/structure/types/manage/page/fields/add-field'); + $label = (string) $definition['label']; if (empty($definition['no_ui'])) { try { - $this->assertSession() - ->elementExists('css', "[name='new_storage_type'][value='$field_type']"); + $this->assertSession()->elementExists('xpath', "//a//span[text()='$label']"); } catch (ElementNotFoundException) { if ($group = $this->getFieldFromGroup($field_type)) { - $this->assertSession() - ->elementExists('css', "[name='new_storage_type'][value='$group']"); - $this->submitForm(['new_storage_type' => $group], 'Continue'); - $this->assertSession() - ->elementExists('css', "[name='group_field_options_wrapper'][value='$field_type']"); - $this->submitForm([], 'Back'); + if ($group !== 'General') { + $link = $this->assertSession()->elementExists('xpath', "//a[.//span[text()='$group']]"); + $link->click(); + $this->assertSession() + ->elementExists('css', "[name='field_options_wrapper'][value='$field_type']"); + } } } } else { - $this->assertSession()->elementNotExists('css', "[name='new_storage_type'][value='$field_type']"); + $this->assertSession()->elementNotExists('xpath', "//a//span[text()='$label']"); } } } @@ -216,10 +216,8 @@ public function testFieldNameValidation(): void { // create a new field with the same name. $url = 'admin/structure/types/manage/' . $this->contentType . '/fields/add-field'; $this->drupalGet($url); - $edit = [ - 'new_storage_type' => 'boolean', - ]; - $this->submitForm($edit, 'Continue'); + $this->clickLink('Boolean'); + $this->submitForm([], 'Continue'); $edit = [ 'label' => $this->randomMachineName(), 'field_name' => 'tags', @@ -232,29 +230,28 @@ public function testFieldNameValidation(): void { $this->config('field_ui.settings')->set('field_prefix', '')->save(); $label = 'Disallowed field'; - $edit1 = [ - 'new_storage_type' => 'test_field', - ]; - $edit2 = [ + $edit = [ 'label' => $label, ]; // Try with an entity key. - $edit2['field_name'] = 'title'; + $edit['field_name'] = 'title'; $bundle_path = 'admin/structure/types/manage/' . $this->contentType; $this->drupalGet("{$bundle_path}/fields/add-field"); - $this->submitForm($edit1, 'Continue'); - $this->submitForm($edit2, 'Continue'); + $this->clickLink('Test field'); + $this->submitForm([], 'Continue'); + $this->submitForm($edit, 'Continue'); $this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.'); // Try with a base field. - $edit2['field_name'] = 'sticky'; + $edit['field_name'] = 'sticky'; $bundle_path = 'admin/structure/types/manage/' . $this->contentType; $this->drupalGet("{$bundle_path}/fields/add-field"); - $this->submitForm($edit1, 'Continue'); - $this->submitForm($edit2, 'Continue'); + $this->clickLink('Test field'); + $this->submitForm([], 'Continue'); + $this->submitForm($edit, 'Continue'); $this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.'); - $this->assertSession()->addressEquals($url); + $this->assertSession()->addressEquals($url . '/test_field/false'); } /** @@ -332,8 +329,8 @@ public function testPreconfiguredFields(): void { // Check that the preconfigured field option exist alongside the regular // field type option. - $this->assertSession()->elementExists('css', "[name='new_storage_type'][value='field_ui:test_field_with_preconfigured_options:custom_options']"); - $this->assertSession()->elementExists('css', "[name='new_storage_type'][value='test_field_with_preconfigured_options']"); + $this->assertSession()->elementExists('xpath', "//a//span[text()='All custom options']"); + $this->assertSession()->elementExists('xpath', "//a//span[text()='Test field with preconfigured options']"); // Add a field with every possible preconfigured value. $this->fieldUIAddNewField(NULL, 'test_custom_options', 'Test label', 'field_ui:test_field_with_preconfigured_options:custom_options'); @@ -368,16 +365,13 @@ public function testFieldPrefix(): void { $field_exceed_max_length_input = $this->randomMachineName(23); // Try to create the field. - $edit1 = [ - 'new_storage_type' => 'test_field', - ]; - $edit2 = [ + $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/add-field'); + $this->clickLink('Test field'); + $edit = [ 'label' => $field_exceed_max_length_label, 'field_name' => $field_exceed_max_length_input, ]; - $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/add-field'); - $this->submitForm($edit1, 'Continue'); - $this->submitForm($edit2, 'Continue'); + $this->submitForm($edit, 'Continue'); $this->assertSession()->pageTextContains('Machine-readable name cannot be longer than 22 characters but is currently 23 characters long.'); // Create a valid field. diff --git a/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php b/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php index e732e009f43add62b3ecaba0601f829b8cb4d0e2..ecdd5a0da2038ee938eaca8a83484bcd1e956e7f 100644 --- a/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php +++ b/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php @@ -139,10 +139,7 @@ public function testAddField(): void { // Make sure field descriptions appear, both 1 line and multiple lines. $this->drupalGet('/admin/structure/types/manage/' . $type->id() . '/fields/add-field'); - $edit = [ - 'new_storage_type' => 'field_test_descriptions', - ]; - $this->submitForm($edit, 'Continue'); + $this->clickLink('Fields for testing descriptions.'); $this->assertSession()->pageTextContains('This one-line field description is important for testing'); $this->assertSession()->pageTextContains('This multiple line description needs to use an array'); $this->assertSession()->pageTextContains('This second line contains important information'); @@ -153,10 +150,8 @@ public function testAddField(): void { $this->assertNull(FieldStorageConfig::loadByName('node', "field_test_field")); $this->drupalGet('/admin/structure/types/manage/' . $type->id() . '/fields/add-field'); - $edit = [ - 'new_storage_type' => 'test_field', - ]; - $this->submitForm($edit, 'Continue'); + $this->clickLink('Test field'); + $this->submitForm([], 'Continue'); $edit = [ 'label' => 'Test field', 'field_name' => 'test_field', @@ -176,10 +171,8 @@ public function testAddField(): void { // Try creating a field with the same machine name. $this->drupalGet('/admin/structure/types/manage/' . $type->id() . '/fields/add-field'); - $edit = [ - 'new_storage_type' => 'test_field', - ]; - $this->submitForm($edit, 'Continue'); + $this->clickLink('Test field'); + $this->submitForm([], 'Continue'); $edit = [ 'label' => 'Test field', 'field_name' => 'test_field', @@ -191,7 +184,7 @@ public function testAddField(): void { // Assert that the field is created with the new settings. $this->submitForm([], 'Update settings'); $this->assertSession()->statusMessageNotContains('Saved'); - $this->submitForm([], 'Save settings'); + $this->submitForm([], 'Save'); $this->assertSession()->statusMessageContains('Saved'); $this->assertEquals(1, FieldStorageConfig::loadByName('node', 'field_test_field')->getCardinality()); @@ -212,10 +205,8 @@ public function testAddFieldWithMultipleUsers(): void { // Start adding a field as user 1, stop prior to saving, but keep the URL. $this->drupalLogin($user1); $this->drupalGet($bundle_path . '/fields/add-field'); - $edit = [ - 'new_storage_type' => 'test_field', - ]; - $this->submitForm($edit, 'Continue'); + $this->clickLink('Test field'); + $this->submitForm([], 'Continue'); $edit = [ 'label' => 'Test field', 'field_name' => 'test_field', @@ -230,10 +221,8 @@ public function testAddFieldWithMultipleUsers(): void { // Actually add a field as user 2. $this->drupalLogin($user2); $this->drupalGet($bundle_path . '/fields/add-field'); - $edit = [ - 'new_storage_type' => 'test_field', - ]; - $this->submitForm($edit, 'Continue'); + $this->clickLink('Test field'); + $this->submitForm([], 'Continue'); $edit = [ 'label' => 'Test field', 'field_name' => 'test_field', @@ -244,7 +233,7 @@ public function testAddFieldWithMultipleUsers(): void { // the field is saved. $this->assertEquals(1, $allowed_no_of_values); $this->submitForm(['field_storage[subform][cardinality_number]' => 2], 'Update settings'); - $this->submitForm([], 'Save settings'); + $this->submitForm([], 'Save'); $this->assertSession()->pageTextContains("Saved Test field configuration."); $this->drupalLogout(); @@ -254,7 +243,7 @@ public function testAddFieldWithMultipleUsers(): void { // Assert that the user can go on with configuring a field with a machine // that is already taken. $this->assertSession()->pageTextNotContains('error'); - $this->submitForm([], 'Save settings'); + $this->submitForm([], 'Save'); // An error is thrown only after the final 'Save'. $this->assertSession()->statusMessageContains("An error occurred while saving the field: 'field_storage_config' entity with ID 'node.field_test_field' already exists."); } @@ -271,10 +260,8 @@ public function testEditFieldWithLeftOverFieldInTempStore(): void { // Start adding a field but stop prior to saving. $this->drupalLogin($user); $this->drupalGet($bundle_path . '/fields/add-field'); - $edit = [ - 'new_storage_type' => 'test_field', - ]; - $this->submitForm($edit, 'Continue'); + $this->clickLink('Test field'); + $this->submitForm([], 'Continue'); $edit = [ 'label' => 'Test field', 'field_name' => 'test_field', @@ -376,7 +363,7 @@ public function testFieldCategoryFallbackWithoutDescription(): void { $node_type = $this->drupalCreateContentType(); $this->drupalLogin($user); $this->drupalGet('/admin/structure/types/manage/' . $node_type->id() . '/fields/add-field'); - $field_type = $this->assertSession()->elementExists('xpath', '//label[text()="Test field"]'); + $field_type = $this->assertSession()->elementExists('xpath', '//span[text()="Test field"]'); $description_container = $field_type->getParent()->find('css', '.field-option__description'); $this->assertNotNull($description_container); $this->assertEquals('', $description_container->getText()); diff --git a/core/modules/field_ui/tests/src/FunctionalJavascript/DefaultValueWidgetTest.php b/core/modules/field_ui/tests/src/FunctionalJavascript/DefaultValueWidgetTest.php index 4db4b2cbb130b57c26d9b4511d4b4bfb077c20bd..ade6ff5e8b096bf7084b99a7f3d640f873c7a82b 100644 --- a/core/modules/field_ui/tests/src/FunctionalJavascript/DefaultValueWidgetTest.php +++ b/core/modules/field_ui/tests/src/FunctionalJavascript/DefaultValueWidgetTest.php @@ -25,6 +25,7 @@ class DefaultValueWidgetTest extends WebDriverTestBase { 'node', 'field_ui', 'taxonomy', + 'block', ]; /** @@ -37,6 +38,7 @@ class DefaultValueWidgetTest extends WebDriverTestBase { */ protected function setUp(): void { parent::setUp(); + $this->drupalPlaceBlock('local_actions_block'); // Create a Content type and two test nodes. $this->createContentType(['type' => 'test_content']); diff --git a/core/modules/field_ui/tests/src/FunctionalJavascript/ManageDisplayTest.php b/core/modules/field_ui/tests/src/FunctionalJavascript/ManageDisplayTest.php index e076b57ceba971792f561d2d9a0ae6d3064d209a..4b6162abfc67a57d40c93d7b9373bc65496bb3f9 100644 --- a/core/modules/field_ui/tests/src/FunctionalJavascript/ManageDisplayTest.php +++ b/core/modules/field_ui/tests/src/FunctionalJavascript/ManageDisplayTest.php @@ -8,7 +8,6 @@ use Drupal\Core\Entity\Entity\EntityFormDisplay; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\Tests\field_ui\Traits\FieldUiJSTestTrait; -use Drupal\Tests\field_ui\Traits\FieldUiTestTrait; // cspell:ignore onewidgetfield @@ -19,7 +18,6 @@ */ class ManageDisplayTest extends WebDriverTestBase { - use FieldUiTestTrait; use FieldUiJSTestTrait; /** @@ -59,6 +57,7 @@ class ManageDisplayTest extends WebDriverTestBase { protected function setUp(): void { parent::setUp(); $this->drupalPlaceBlock('system_breadcrumb_block'); + $this->drupalPlaceBlock('local_actions_block'); // Create a test user. $admin_user = $this->drupalCreateUser([ diff --git a/core/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php b/core/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php index ed59877c5577368ef250df8d318f70679569ea92..c469e83ec915ce4cf34483dce043c53dbda9c937 100644 --- a/core/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php +++ b/core/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php @@ -81,6 +81,7 @@ protected function setUp(): void { $this->type2 = $type2->id(); $this->entityTypeManager = $this->container->get('entity_type.manager'); + $this->getSession()->resizeWindow(1100, 800); } /** @@ -178,47 +179,57 @@ public function testAddField(): void { $page = $this->getSession()->getPage(); $assert_session = $this->assertSession(); - $this->drupalGet('admin/structure/types/manage/article/fields/add-field'); + $this->drupalGet('admin/structure/types/manage/article/fields'); + + $this->clickLink('Create a new field'); + $field_name = 'test_field_1'; + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->clickLink('Number'); + $assert_session->assertWaitOnAjaxRequest(); + $assert_session->waitForText('Add field: Number'); + $buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane'); // Test validation. - $page->pressButton('Continue'); + $buttons->pressButton('Continue'); + $assert_session->assertWaitOnAjaxRequest(); + $assert_session->pageTextContains('Label field is required.'); $assert_session->pageTextContains('You need to select a field type.'); - $assert_session->pageTextNotContains('Choose an option below'); - - $this->assertNotEmpty($number_field = $page->find('xpath', '//*[text() = "Number"]')->getParent()); - $number_field->click(); - $this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="number"]')->isSelected()); - $page->pressButton('Continue'); - $assert_session->pageTextContains('Choose an option below'); - $field_name = 'test_field_1'; + $assert_session->elementExists('css', '[name="label"].error'); + $assert_session->elementExists('css', '[name="field_options_wrapper"].error'); $page->fillField('label', $field_name); - $page->pressButton('Continue'); - $assert_session->pageTextContains('You need to choose an option.'); - $assert_session->elementNotExists('css', '[name="new_storage_type"].error'); - $assert_session->elementExists('css', '[name="group_field_options_wrapper"].error'); - $page->pressButton('Back'); + $buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane'); + $buttons->pressButton('Continue'); + $assert_session->assertWaitOnAjaxRequest(); + + $assert_session->pageTextContains('You need to select a field type.'); + + $assert_session->elementNotExists('css', '[name="label"].error'); + $assert_session->elementExists('css', '[name="field_options_wrapper"].error'); + $buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane'); + $buttons->pressButton('Change field type'); + $this->assertSession()->assertWaitOnAjaxRequest(); // Try adding a field using a grouped field type. - $this->assertNotEmpty($email_field = $page->find('xpath', '//*[text() = "Email"]')->getParent()); - $email_field->click(); - $this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="email"]')->isSelected()); - $page->pressButton('Continue'); - $assert_session->pageTextNotContains('Choose an option below'); - $page->pressButton('Back'); - - $this->assertNotEmpty($text = $page->find('xpath', '//*[text() = "Plain text"]')->getParent()); - $text->click(); - $this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="plain_text"]')->isSelected()); - $page->pressButton('Continue'); - $assert_session->pageTextContains('Choose an option below'); + $this->clickLink('Email'); + $assert_session->assertWaitOnAjaxRequest(); + $assert_session->pageTextNotContains('Choose a field type'); + $assert_session->elementExists('css', '[name="label"]'); + $buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane'); + $buttons->pressButton('Change field type'); + $this->assertSession()->assertWaitOnAjaxRequest(); + + $this->clickLink('Plain text'); + $assert_session->assertWaitOnAjaxRequest(); + $assert_session->pageTextContains('Choose a field type'); + $assert_session->elementExists('css', '[name="label"]'); $page->fillField('label', $field_name); $this->assertNotEmpty($text_plain = $page->find('xpath', '//*[text() = "Text (plain)"]')->getParent()); $text_plain->click(); - $this->assertTrue($assert_session->elementExists('css', '[name="group_field_options_wrapper"][value="string"]')->isSelected()); - $page->pressButton('Continue'); - - $this->assertMatchesRegularExpression('/.*article\/add-field\/node\/field_test_field_1.*/', $this->getUrl()); + $this->assertTrue($assert_session->elementExists('css', '[name="field_options_wrapper"][value="string"]')->isSelected()); + $buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane'); + $buttons->pressButton('Continue'); + $this->assertSession()->assertWaitOnAjaxRequest(); // Ensure the default value is reloaded when the field storage settings // are changed. @@ -260,36 +271,41 @@ public function testAddField(): void { // Set a default value that is under the new limit. $default_input_1->setValue('Five!'); - $page->pressButton('Save settings'); - $assert_session->pageTextContains('Saved ' . $field_name . ' configuration.'); + $buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane'); + $buttons->pressButton('Save'); + $this->assertTrue($assert_session->waitForText('Saved ' . $field_name . ' configuration.')); $this->assertNotNull($field_storage = FieldStorageConfig::loadByName('node', "field_$field_name")); $this->assertEquals('string', $field_storage->getType()); // Try adding a field using a non-grouped field type. - $this->drupalGet('admin/structure/types/manage/article/fields/add-field'); - + $this->drupalGet('admin/structure/types/manage/article/fields'); + $this->clickLink('Create a new field'); + $this->assertSession()->assertWaitOnAjaxRequest(); $this->assertNotEmpty($number_field = $page->find('xpath', '//*[text() = "Number"]')->getParent()); $number_field->click(); - $this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="number"]')->isSelected()); - $page->pressButton('Continue'); - $assert_session->pageTextContains('Choose an option below'); + $assert_session->assertWaitOnAjaxRequest(); + $assert_session->pageTextContains('Choose a field type'); + $this->assertNotEmpty($number_integer = $page->find('xpath', '//*[text() = "Number (integer)"]')->getParent()); $number_integer->click(); - $this->assertTrue($assert_session->elementExists('css', '[name="group_field_options_wrapper"][value="integer"]')->isSelected()); + $this->assertTrue($assert_session->elementExists('css', '[name="field_options_wrapper"][value="integer"]')->isSelected()); - $page->pressButton('Back'); + $buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane'); + $buttons->pressButton('Change field type'); + $this->assertSession()->assertWaitOnAjaxRequest(); $this->assertNotEmpty($test_field = $page->find('xpath', '//*[text() = "Test field"]')->getParent()); $test_field->click(); - $this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="test_field"]')->isSelected()); - $page->pressButton('Continue'); + $assert_session->assertWaitOnAjaxRequest(); $field_name = 'test_field_2'; $page->fillField('label', $field_name); - $assert_session->pageTextNotContains('Choose an option below'); - - $page->pressButton('Continue'); - $this->assertMatchesRegularExpression('/.*article\/add-field\/node\/field_test_field_2.*/', $this->getUrl()); - $page->pressButton('Save settings'); - $assert_session->pageTextContains('Saved ' . $field_name . ' configuration.'); + $assert_session->pageTextNotContains('Choose a field type'); + + $buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane'); + $buttons->pressButton('Continue'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane'); + $buttons->pressButton('Save'); + $this->assertTrue($assert_session->waitForText('Saved ' . $field_name . ' configuration.')); $this->assertNotNull($field_storage = FieldStorageConfig::loadByName('node', "field_$field_name")); $this->assertEquals('test_field', $field_storage->getType()); } @@ -298,28 +314,28 @@ public function testAddField(): void { * Tests the order in which the field types appear in the form. */ public function testFieldTypeOrder(): void { - $this->drupalGet('admin/structure/types/manage/article/fields/add-field'); - $page = $this->getSession()->getPage(); $field_type_categories = [ - 'selection_list', - 'number', + 'Selection list', + 'Number', ]; foreach ($field_type_categories as $field_type_category) { + $page = $this->getSession()->getPage(); + $assert_session = $this->assertSession(); + $this->drupalGet('admin/structure/types/manage/article/fields/add-field'); // Select the group card. - $group_field_card = $page->find('css', "[name='new_storage_type'][value='$field_type_category']")->getParent(); - $group_field_card->click(); - $page->pressButton('Continue'); + $this->clickLink($field_type_category); + $this->assertSession()->assertWaitOnAjaxRequest(); $field_types = $page->findAll('css', '.subfield-option .option'); $field_type_labels = []; foreach ($field_types as $field_type) { $field_type_labels[] = $field_type->getText(); } $expected_field_types = match ($field_type_category) { - 'selection_list' => [ + 'Selection list' => [ 'List (text)', 'List (integer)', ], - 'number' => [ + 'Number' => [ 'Number (integer)', 'Number (decimal)', ], @@ -327,7 +343,8 @@ public function testFieldTypeOrder(): void { // Assert that the field type options are displayed as per their weights. $this->assertSame($expected_field_types, $field_type_labels); // Return to the first step of the form. - $page->pressButton('Back'); + $assert_session->buttonExists('Change field type')->press(); + $assert_session->assertWaitOnAjaxRequest(); } } @@ -357,17 +374,18 @@ public function testAllowedValuesFormValidation(): void { * Tests the form validation for label field. */ public function testLabelFieldFormValidation(): void { - $this->drupalGet('/admin/structure/types/manage/article/fields/add-field'); + $this->drupalGet('/admin/structure/types/manage/article/fields'); $page = $this->getSession()->getPage(); - - $page->findButton('Continue')->click(); + $page->clickLink('Create a new field'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->clickLink('Plain text'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane'); + $buttons->pressButton('Continue'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->assertSession()->pageTextContains('Label field is required.'); + $this->assertSession()->pageTextContains('Machine-readable name field is required.'); $this->assertSession()->pageTextContains('You need to select a field type.'); - - $this->assertNotEmpty($boolean_field = $page->find('xpath', '//*[text() = "Boolean (overridden by alter)"]')->getParent()); - $boolean_field->click(); - $page->findButton('Continue')->click(); - $page->findButton('Continue')->click(); - $this->assertSession()->pageTextContains('Add new field: you need to provide a label.'); } } diff --git a/core/modules/field_ui/tests/src/Traits/FieldUiJSTestTrait.php b/core/modules/field_ui/tests/src/Traits/FieldUiJSTestTrait.php index c611e00adedeb6505b04db3d1c7a8510eced9d07..f3eec28138c532d21732e9aacf9a1f7a55bd67a0 100644 --- a/core/modules/field_ui/tests/src/Traits/FieldUiJSTestTrait.php +++ b/core/modules/field_ui/tests/src/Traits/FieldUiJSTestTrait.php @@ -4,10 +4,13 @@ namespace Drupal\Tests\field_ui\Traits; +use Behat\Mink\Exception\ElementNotFoundException; + /** * Provides common functionality for the Field UI tests that depend on JS. */ trait FieldUiJSTestTrait { + use FieldUiTestTrait; /** * Creates a new field through the Field UI. @@ -28,13 +31,16 @@ trait FieldUiJSTestTrait { * @throws \Behat\Mink\Exception\ElementNotFoundException */ public function fieldUIAddNewFieldJS(?string $bundle_path, string $field_name, ?string $label = NULL, string $field_type = 'test_field', bool $save_settings = TRUE): void { + $this->getSession()->resizeWindow(1200, 800); $label = $label ?: $field_name; // Allow the caller to set a NULL path in case they navigated to the right // page before calling this method. if ($bundle_path !== NULL) { - $bundle_path = "$bundle_path/fields/add-field"; + $bundle_path = "$bundle_path/fields"; $this->drupalGet($bundle_path); + $this->getSession()->getPage()->clickLink('Create a new field'); + $this->assertSession()->assertWaitOnAjaxRequest(); } // First step: 'Add field' page. @@ -43,16 +49,27 @@ public function fieldUIAddNewFieldJS(?string $bundle_path, string $field_name, ? $page = $session->getPage(); $assert_session = $this->assertSession(); - if ($assert_session->waitForElementVisible('css', "[name='new_storage_type'][value='$field_type']")) { - $page = $this->getSession()->getPage(); - $field_card = $page->find('css', "[name='new_storage_type'][value='$field_type']")->getParent(); + try { + /** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_plugin_manager */ + $field_type_plugin_manager = \Drupal::service('plugin.manager.field.field_type'); + $field_definitions = $field_type_plugin_manager->getUiDefinitions(); + $field_type_label = (string) $field_definitions[$field_type]['label']; + $this->getSession()->getPage()->clickLink($field_type_label); + $this->assertSession()->assertWaitOnAjaxRequest(); + + if ($this->getSession()->getPage()->hasField('field_options_wrapper')) { + $this->assertSession()->fieldExists('field_options_wrapper')->selectOption($field_type); + } } - else { - $field_card = $this->getFieldFromGroupJS($field_type); + // If the element could not be found then it is probably in a group. + catch (ElementNotFoundException) { + // Call the helper function to confirm it is in a group. + $field_group = $this->getFieldFromGroup($field_type); + $this->clickLink($field_group); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->assertSession()->fieldExists('field_options_wrapper')->selectOption($field_type); } - $field_card?->click(); - $page->findButton('Continue')->click(); - $field_label = $page->findField('edit-label'); + $field_label = $page->findField('label'); $this->assertTrue($field_label->isVisible()); $field_label = $page->find('css', 'input[data-drupal-selector="edit-label"]'); $field_label->setValue($label); @@ -64,12 +81,16 @@ public function fieldUIAddNewFieldJS(?string $bundle_path, string $field_name, ? $this->assertTrue($field_field_name->isVisible()); $field_field_name->setValue($field_name); - $page->findButton('Continue')->click(); + $this->assertSession()->elementExists('xpath', '//button[text()="Continue"]')->press(); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->assertSession()->waitForElementVisible('css', '#drupal-modal'); + $assert_session->waitForText("These settings apply to the $label field everywhere it is used."); if ($save_settings) { // Second step: Save field settings. - $page->findButton('Save settings')->click(); - $assert_session->pageTextContains("Saved $label configuration."); + $save_button = $page->find('css', '.ui-dialog-buttonpane')->findButton('Save'); + $save_button->click(); + $assert_session->assert($assert_session->waitForText("Saved $label configuration."), 'text not found'); // Check that the field appears in the overview form. $row = $page->find('css', '#field-' . $field_name); @@ -113,7 +134,7 @@ public function fieldUIAddExistingFieldJS(string $bundle_path, string $existing_ // Second step: 'Field settings' form. $this->submitForm($field_edit, 'Save settings'); - $this->assertSession()->pageTextContains("Saved $label configuration."); + $this->assertSession()->assert($this->assertSession()->waitForText("Saved $label configuration."), 'text not found'); // Check that the field appears in the overview form. $xpath = $this->assertSession()->buildXPathQuery("//table[@id=\"field-overview\"]//tr/td[1 and text() = :label]", [ @@ -122,33 +143,4 @@ public function fieldUIAddExistingFieldJS(string $bundle_path, string $existing_ $this->assertSession()->elementExists('xpath', $xpath); } - /** - * Helper function that returns the field card element if it is in a group. - * - * @param string $field_type - * The name of the field type. - * - * @return \Behat\Mink\Element\NodeElement|false|mixed|null - * Field card element within a group. - */ - public function getFieldFromGroupJS($field_type) { - $group_elements = $this->getSession()->getPage()->findAll('css', '.field-option-radio'); - $groups = []; - foreach ($group_elements as $group_element) { - $groups[] = $group_element->getAttribute('value'); - } - $field_card = NULL; - foreach ($groups as $group) { - $group_field_card = $this->getSession()->getPage()->find('css', "[name='new_storage_type'][value='$group']")->getParent(); - $group_field_card->click(); - $this->getSession()->getPage()->pressButton('Continue'); - $field_card = $this->getSession()->getPage()->find('css', "[name='group_field_options_wrapper'][value='$field_type']"); - if ($field_card) { - break; - } - $this->getSession()->getPage()->pressButton('Back'); - } - return $field_card->getParent(); - } - } diff --git a/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php b/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php index 905c645083b228ed115a17a855fdb876c17671ac..f31ed77193f0beea36872f7d76a94af554fcec66 100644 --- a/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php +++ b/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php @@ -39,9 +39,6 @@ public function fieldUIAddNewField($bundle_path, $field_name, $label = NULL, $fi // See https://www.drupal.org/project/drupal/issues/3030902 $label = $label ?: $this->randomMachineName(); $initial_edit = [ - 'new_storage_type' => $field_type, - ]; - $second_edit = [ 'label' => $label, 'field_name' => $field_name, ]; @@ -53,29 +50,29 @@ public function fieldUIAddNewField($bundle_path, $field_name, $label = NULL, $fi // First step: 'Add field' page. $this->drupalGet($bundle_path); } - else { - $bundle_path = $this->getUrl(); - } try { // First check if the passed in field type is not part of a group. - $this->assertSession()->elementExists('css', "[name='new_storage_type'][value='$field_type']"); + /** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_plugin_manager */ + $field_type_plugin_manager = \Drupal::service('plugin.manager.field.field_type'); + $field_definitions = $field_type_plugin_manager->getUiDefinitions(); + $field_type_label = (string) $field_definitions[$field_type]['label']; + $link = $this->assertSession()->elementExists('xpath', "//a[.//span[text()='$field_type_label']]"); + $link->click(); + + if ($this->getSession()->getPage()->hasField('field_options_wrapper')) { + $initial_edit['field_options_wrapper'] = $field_type; + } } // If the element could not be found then it is probably in a group. catch (ElementNotFoundException) { // Call the helper function to confirm it is in a group. $field_group = $this->getFieldFromGroup($field_type); - if ($field_group) { - // Pass in the group name as the new storage type. - $initial_edit['new_storage_type'] = $field_group; - $second_edit['group_field_options_wrapper'] = $field_type; - $this->drupalGet($bundle_path); - } + // Pass in the group name as the new storage type. + $this->clickLink($field_group); + $initial_edit['field_options_wrapper'] = $field_type; } $this->submitForm($initial_edit, 'Continue'); - $this->submitForm($second_edit, 'Continue'); - // Assert that the field is not created. - $this->assertFieldDoesNotExist($bundle_path, $label); if ($save_settings) { $this->assertSession()->pageTextContains("These settings apply to the $label field everywhere it is used."); @@ -99,7 +96,7 @@ public function fieldUIAddNewField($bundle_path, $field_name, $label = NULL, $fi $this->submitForm($prefixed_storage_edit, 'Update settings'); // Third step: 'Field settings' form. - $this->submitForm($field_edit, 'Save settings'); + $this->submitForm($field_edit, 'Save'); $this->assertSession()->pageTextContains("Saved $label configuration."); // Check that the field appears in the overview form. @@ -186,48 +183,21 @@ public function fieldUIDeleteField($bundle_path, $field_name, $label, $bundle_la * @param string $field_type * The name of the field type. * - * @return string + * @return string|null * Group name */ public function getFieldFromGroup($field_type) { - $group_elements = $this->getSession()->getPage()->findAll('css', '.field-option-radio'); - $groups = []; - foreach ($group_elements as $group_element) { - $groups[] = $group_element->getAttribute('value'); - } - foreach ($groups as $group) { - $test = [ - 'new_storage_type' => $group, - ]; - $this->submitForm($test, 'Continue'); - try { - $this->assertSession()->elementExists('css', "[name='group_field_options_wrapper'][value='$field_type']"); - $this->submitForm([], 'Back'); + /** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_plugin_manager */ + $field_type_plugin_manager = \Drupal::service('plugin.manager.field.field_type'); + $grouped_field_types = $field_type_plugin_manager->getGroupedDefinitions($field_type_plugin_manager->getUiDefinitions()); + foreach ($grouped_field_types as $group => $field_types) { + if (array_key_exists($field_type, $field_types)) { return $group; } - catch (ElementNotFoundException) { - $this->submitForm([], 'Back'); - continue; - } } return NULL; } - /** - * Asserts that the field doesn't exist in the overview form. - * - * @param string $bundle_path - * The bundle path. - * @param string $label - * The field label. - */ - protected function assertFieldDoesNotExist(string $bundle_path, string $label) { - $original_url = $this->getUrl(); - $this->drupalGet(explode('/fields', $bundle_path)[0] . '/fields'); - $this->assertFieldDoesNotExistOnOverview($label); - $this->drupalGet($original_url); - } - /** * Asserts that the field appears on the overview form. * diff --git a/core/modules/media/src/Hook/MediaHooks.php b/core/modules/media/src/Hook/MediaHooks.php index d6b657d9d09e6ee31bd856b0a29c4ab82f20b441..9a80bfec1583a213c1f1aa16c23e29fe13dec371 100644 --- a/core/modules/media/src/Hook/MediaHooks.php +++ b/core/modules/media/src/Hook/MediaHooks.php @@ -154,8 +154,8 @@ public function formFieldUiFieldStorageAddFormAlter(&$form, FormStateInterface $ ]); } $field_types = ['file_upload', 'field_ui:entity_reference:media']; - if (in_array($form_state->getValue('new_storage_type'), $field_types)) { - $form['group_field_options_wrapper']['description_wrapper'] = ['#type' => 'item', '#markup' => $description_text]; + if (in_array($form_state->getStorage()['field_type'], $field_types)) { + $form['field_options_wrapper']['description_wrapper'] = ['#type' => 'item', '#markup' => $description_text]; } } diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaReferenceFieldHelpTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaReferenceFieldHelpTest.php index be1e173d6d28c023b8160f2fba98ac73f2ad5385..80d438685ddd2233a9843ccf86ffb5d8be821b79 100644 --- a/core/modules/media/tests/src/FunctionalJavascript/MediaReferenceFieldHelpTest.php +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaReferenceFieldHelpTest.php @@ -20,6 +20,8 @@ class MediaReferenceFieldHelpTest extends MediaJavascriptTestBase { * {@inheritdoc} */ protected static $modules = [ + 'block', + 'field_ui', 'media', 'media_library', ]; @@ -30,38 +32,39 @@ class MediaReferenceFieldHelpTest extends MediaJavascriptTestBase { * @see media_form_field_ui_field_storage_add_form_alter() */ public function testFieldCreationHelpText(): void { + $this->drupalPlaceBlock('local_actions_block'); $assert_session = $this->assertSession(); - $page = $this->getSession()->getPage(); $type = $this->drupalCreateContentType([ 'type' => 'foo', ]); - $this->drupalGet("/admin/structure/types/manage/{$type->id()}/fields/add-field"); + $this->drupalGet("/admin/structure/types/manage/{$type->id()}/fields"); + $this->clickLink('Create a new field'); + $this->assertSession()->assertWaitOnAjaxRequest(); $field_groups = [ - 'file_upload', - 'field_ui:entity_reference:media', + 'File upload', + 'Media', ]; $help_text = 'Use Media reference fields for most files, images, audio, videos, and remote media. Use File or Image reference fields when creating your own media types, or for legacy files and images created before installing the Media module.'; // Choose a boolean field, none of the description containers should be // visible. - $assert_session->elementExists('css', "[name='new_storage_type'][value='boolean']"); - $page->find('css', "[name='new_storage_type'][value='boolean']")->getParent()->click(); - $page->pressButton('Continue'); + $this->clickLink('Boolean'); + $assert_session->assertWaitOnAjaxRequest(); $assert_session->pageTextNotContains($help_text); - $page->pressButton('Back'); + $assert_session->buttonExists('Change field type')->press(); + $assert_session->assertWaitOnAjaxRequest(); // Select each of the Reference, File upload field groups and verify their // descriptions are now visible and match the expected text. foreach ($field_groups as $field_group) { - $assert_session->elementExists('css', "[name='new_storage_type'][value='$field_group']"); - $page->find('css', "[name='new_storage_type'][value='$field_group']")->getParent()->click(); - - $page->pressButton('Continue'); + $this->clickLink($field_group); + $assert_session->assertWaitOnAjaxRequest(); $assert_session->pageTextContains($help_text); - $page->pressButton('Back'); + $assert_session->buttonExists('Change field type')->press(); + $assert_session->assertWaitOnAjaxRequest(); } } diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php index f30393116782a969f5527c1543a67b3ef8b0529b..7bd1e52628e653ad296429eddbf468f362b818f5 100644 --- a/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php +++ b/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php @@ -70,13 +70,12 @@ public function testFieldUiIntegration(): void { $this->drupalLogin($user); $this->drupalGet('/admin/structure/types/manage/article/fields/add-field'); - $page->find('css', "[name='new_storage_type'][value='field_ui:entity_reference:media']")->getParent()->click(); - $page->findButton('Continue')->click(); - $this->assertNotNull($assert_session->waitForField('label')); + $this->clickLink('Media'); + $this->assertSession()->assertWaitOnAjaxRequest(); $page->fillField('label', 'Shatner'); $this->waitForText('field_shatner'); - $page->pressButton('Continue'); - $this->assertMatchesRegularExpression('/.*article\/add-field\/node\/field_shatner.*/', $this->getUrl()); + $this->assertSession()->elementExists('xpath', '//button[text()="Continue"]')->press(); + $this->assertSession()->assertWaitOnAjaxRequest(); $assert_session->pageTextNotContains('Undefined index: target_bundles'); $this->waitForFieldExists('Type One')->check(); $this->assertElementExistsAfterWait('css', '[name="settings[handler_settings][target_bundles][type_one]"][checked="checked"]'); @@ -84,8 +83,8 @@ public function testFieldUiIntegration(): void { $this->assertElementExistsAfterWait('css', '[name="settings[handler_settings][target_bundles][type_two]"][checked="checked"]'); $page->checkField('settings[handler_settings][target_bundles][type_three]'); $this->assertElementExistsAfterWait('css', '[name="settings[handler_settings][target_bundles][type_three]"][checked="checked"]'); - $page->pressButton('Save settings'); - $assert_session->pageTextContains('Saved Shatner configuration.'); + $page->find('css', '.ui-dialog-buttonset')->pressButton('Save'); + $this->assertTrue($assert_session->waitForText('Saved Shatner configuration.')); $this->drupalGet('/admin/structure/types/manage/article/fields/node.article.field_shatner'); $assert_session->checkboxNotChecked('set_default_value'); @@ -97,7 +96,7 @@ public function testFieldUiIntegration(): void { $this->pressInsertSelected('Added one media item.'); $page->pressButton('Save settings'); - $assert_session->pageTextContains('Saved Shatner configuration.'); + $this->assertTrue($assert_session->waitForText('Saved Shatner configuration.')); $this->drupalGet('/admin/structure/types/manage/article/fields/node.article.field_shatner'); $assert_session->checkboxChecked('set_default_value'); @@ -110,6 +109,7 @@ public function testFieldUiIntegration(): void { $this->waitForFieldExists('Type One')->check(); $this->assertElementExistsAfterWait('css', '[name="settings[handler_settings][target_bundles][type_one]"][checked="checked"]'); $page->pressButton('Save settings'); + $this->assertTrue($assert_session->waitForText('Saved Shatner configuration.')); $this->drupalGet('/admin/structure/types/manage/page/form-display'); $assert_session->fieldValueEquals('fields[field_shatner][type]', 'media_library_widget'); } diff --git a/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php b/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php index 31580f03c0168072540a20ad6a45bbbad72a7b6a..8fcd771de519ff0005bddb753882dda7a0c991dc 100644 --- a/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php +++ b/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php @@ -173,15 +173,14 @@ public function testNodeTypeTitleLabelTranslation(): void { // Add an email field. $this->drupalGet("admin/structure/types/manage/{$type}/fields/add-field"); - $this->submitForm([ - 'new_storage_type' => 'email', - ], 'Continue'); + $this->clickLink('Email'); + $this->submitForm([], 'Continue'); $this->submitForm([ 'label' => 'Email', 'field_name' => 'email', ], 'Continue'); $this->submitForm([], 'Update settings'); - $this->submitForm([], 'Save settings'); + $this->submitForm([], 'Save'); $type = $this->randomMachineName(16); $name = $this->randomString(); diff --git a/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUIAllowedValuesTest.php b/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUIAllowedValuesTest.php index 5266f1c950aa993c135c4bf8c2d25336fd76c059..80f92c2d28685dd02fa70dfa227c2a3543e9cfc2 100644 --- a/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUIAllowedValuesTest.php +++ b/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUIAllowedValuesTest.php @@ -26,6 +26,7 @@ class OptionsFieldUIAllowedValuesTest extends WebDriverTestBase { 'node', 'options', 'field_ui', + 'block', ]; /** @@ -66,6 +67,7 @@ class OptionsFieldUIAllowedValuesTest extends WebDriverTestBase { */ protected function setUp(): void { parent::setUp(); + $this->drupalPlaceBlock('local_actions_block'); // Create test user. $admin_user = $this->drupalCreateUser([ @@ -170,7 +172,8 @@ public function testOptionsAllowedValues($option_type, $options, $is_string_opti $this->assertHasFocusByAttribute('name', $key_element_name); $this->assertAllowValuesRowCount($expected_rows); } - $page->pressButton('Save'); + $page->pressButton('Save settings'); + $this->assertTrue($this->assertSession()->waitForText('Saved field_options_text configuration.')); $option_labels = array_values($options); $this->assertCount(3, $option_labels); @@ -188,7 +191,8 @@ public function testOptionsAllowedValues($option_type, $options, $is_string_opti // Change the order the items appear. $drag_handle->dragTo($target); $this->assertOrder([$option_labels[1], $option_labels[2], $option_labels[0], ''], $is_string_option); - $page->pressButton('Save'); + $page->pressButton('Save settings'); + $this->assertTrue($this->assertSession()->waitForText('Saved field_options_text configuration.')); $this->drupalGet($this->nodeFormPath); $this->assertNodeFormOrder(['- None -', $option_labels[1], $option_labels[2], $option_labels[0]]); @@ -202,7 +206,8 @@ public function testOptionsAllowedValues($option_type, $options, $is_string_opti $page->pressButton('remove_row_button__1'); $this->assertSession()->assertWaitOnAjaxRequest(); $this->assertOrder([$option_labels[1], $option_labels[0], ''], $is_string_option); - $page->pressButton('Save'); + $page->pressButton('Save settings'); + $this->assertTrue($this->assertSession()->waitForText('Saved field_options_text configuration.')); $this->drupalGet($this->nodeFormPath); $this->assertNodeFormOrder(['- None -', $option_labels[1], $option_labels[0]]); diff --git a/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php b/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php index d1facffccd6a81e2b09bdc60ddda4bef4fe3532d..25dcc1d2ebc683376a2d44dcf8dfd5db65bb1281 100644 --- a/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php +++ b/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php @@ -26,6 +26,7 @@ class OptionsFieldUITest extends WebDriverTestBase { 'node', 'options', 'field_ui', + 'block', ]; /** @@ -66,6 +67,7 @@ class OptionsFieldUITest extends WebDriverTestBase { */ protected function setUp(): void { parent::setUp(); + $this->drupalPlaceBlock('local_actions_block'); // Create test user. $admin_user = $this->drupalCreateUser([ @@ -94,31 +96,36 @@ public function testDefaultValueOptions(): void { $page->findField('set_default_value')->setValue(TRUE); // Assert that the option added in the subform is available to the default // value field. - $this->assertSession()->optionExists('default_value_input[field_test_string_list]', 'first'); + $this->assertSession()->waitForElement('css', '[name="default_value_input[field_test_int_list]"] option:contains("first")'); $page->pressButton('Add another item'); - $this->assertNotNull($assert_session->waitForElement('css', "[name='field_storage[subform][settings][allowed_values][table][1][item][label]']")); - $page->findField('field_storage[subform][settings][allowed_values][table][1][item][label]')->setValue('second'); - $assert_session->assertWaitOnAjaxRequest(); + $assert_session->waitForField('field_storage[subform][settings][allowed_values][table][1][item][label]'); + $field_input = $page->find('css', '[name="field_storage[subform][settings][allowed_values][table][1][item][label]"]'); + $field_input->setValue('second'); + $this->assertNotNull($assert_session->waitForElement('css', '[name="default_value_input[field_test_string_list]"] option:contains("second")')); $assert_session->optionExists('default_value_input[field_test_string_list]', 'second'); $page->selectFieldOption('default_value_input[field_test_string_list]', 'second'); - $page->pressButton('Save settings'); - $assert_session->pageTextContains('Saved Test string list configuration.'); + $page->find('css', '.ui-dialog-buttonset button:contains("Save")')->press(); + $this->assertTrue($assert_session->waitForText('Saved Test string list configuration.')); // Create a field of type list:integer. $this->fieldUIAddNewFieldJS($bundle_path, 'test_int_list', 'Test int list', 'list_integer', FALSE); $page->findField('field_storage[subform][settings][allowed_values][table][0][item][label]')->setValue('first'); $assert_session->assertWaitOnAjaxRequest(); + $page->findField('set_default_value')->setValue(TRUE); + $page->selectFieldOption('default_value_input[field_test_int_list]', 'first'); // Assert that no validation is performed. $assert_session->statusMessageNotContains('Value field is required.'); - $page->findField('field_storage[subform][settings][allowed_values][table][0][item][key]')->setValue(1); - $assert_session->assertWaitOnAjaxRequest(); - $page->findField('set_default_value')->setValue(TRUE); + $field_input = $page->find('css', '[name="field_storage[subform][settings][allowed_values][table][0][item][key]"]'); + $field_input->setValue(1); + $this->assertSession()->assertWaitOnAjaxRequest(); + $page->checkField('set_default_value'); + $this->assertSession()->waitForElement('css', '[name="default_value_input[field_test_int_list]"] option:contains("first")'); // Assert that the option added in the subform is available to the default // value field. $this->assertSession()->optionExists('default_value_input[field_test_int_list]', 'first'); $page->selectFieldOption('default_value_input[field_test_int_list]', 'first'); - $page->pressButton('Save settings'); - $assert_session->pageTextContains('Saved Test int list configuration.'); + $page->find('css', '.ui-dialog-buttonset button:contains("Save")')->press(); + $this->assertTrue($this->assertSession()->waitForText('Saved Test int list configuration.')); } /** diff --git a/core/modules/responsive_image/tests/src/FunctionalJavascript/ResponsiveImageFieldUiTest.php b/core/modules/responsive_image/tests/src/FunctionalJavascript/ResponsiveImageFieldUiTest.php index 8093b37bd4a2a2c67981d13e8aedbdefb1a63e7f..0e85555f524456e2b53a444903060f2693be82a9 100644 --- a/core/modules/responsive_image/tests/src/FunctionalJavascript/ResponsiveImageFieldUiTest.php +++ b/core/modules/responsive_image/tests/src/FunctionalJavascript/ResponsiveImageFieldUiTest.php @@ -47,6 +47,7 @@ class ResponsiveImageFieldUiTest extends WebDriverTestBase { protected function setUp(): void { parent::setUp(); $this->drupalPlaceBlock('system_breadcrumb_block'); + $this->drupalPlaceBlock('local_actions_block'); // Create a test user. $admin_user = $this->drupalCreateUser([ 'access content', @@ -141,8 +142,8 @@ public function testResponsiveImageFormatterUi(): void { // Save the form to save the settings. $page->pressButton('Save'); - $assert_session->responseContains('Responsive image style: Style One'); - $assert_session->responseContains('Linked to content'); + $this->assertTrue($assert_session->waitForText('Responsive image style: Style One')); + $this->assertTrue($assert_session->waitForText('Linked to content')); $page->find('css', '#edit-fields-field-image-settings-edit')->click(); $assert_session->waitForField('fields[field_image][settings_edit_form][settings][responsive_image_style]'); @@ -151,8 +152,8 @@ public function testResponsiveImageFormatterUi(): void { // Save the form to save the settings. $page->pressButton('Save'); - $assert_session->responseContains('Responsive image style: Style One'); - $assert_session->responseContains('Linked to file'); + $this->assertTrue($assert_session->waitForText('Responsive image style: Style One')); + $this->assertTrue($assert_session->waitForText('Linked to file')); } }