diff --git a/core/modules/media/config/install/core.entity_view_mode.media.full.yml b/core/modules/media/config/optional/core.entity_form_mode.media.add_inline.yml similarity index 60% copy from core/modules/media/config/install/core.entity_view_mode.media.full.yml copy to core/modules/media/config/optional/core.entity_form_mode.media.add_inline.yml index dfdbb3a..e082019 100644 --- a/core/modules/media/config/install/core.entity_view_mode.media.full.yml +++ b/core/modules/media/config/optional/core.entity_form_mode.media.add_inline.yml @@ -1,9 +1,9 @@ langcode: en -status: false +status: true dependencies: module: - media -id: media.full -label: 'Full content' +id: media.add_inline +label: 'File Upload' targetEntityType: media cache: true diff --git a/core/modules/media/config/install/core.entity_view_mode.media.full.yml b/core/modules/media/config/optional/core.entity_view_mode.media.full.yml similarity index 100% rename from core/modules/media/config/install/core.entity_view_mode.media.full.yml rename to core/modules/media/config/optional/core.entity_view_mode.media.full.yml diff --git a/core/modules/media/src/Element/MediaManagedFile.php b/core/modules/media/src/Element/MediaManagedFile.php new file mode 100644 index 0000000..07b5148 --- /dev/null +++ b/core/modules/media/src/Element/MediaManagedFile.php @@ -0,0 +1,49 @@ +getSource()->getConfiguration()['source_field']; + $file_value = $media_entity->get($media_source_field)->getValue(); + // @TODO: Should / could we assume here that this will always have only + // 1 value? + foreach ($file_value as $value) { + $element['#value']['fids'][] = $value['target_id']; + } + } + $element = parent::processManagedFile($element, $form_state, $complete_form); + + // We don't want to show the file-upload field for values that already have + // a file. + if (!empty($element['#files'])) { + $element['upload']['#access'] = FALSE; + } + + $mids = isset($element['#value']['mids']) ? $element['#value']['mids'] : []; + $element['mids'] = [ + '#type' => 'hidden', + '#value' => $mids, + ]; + + return $element; + } + +} diff --git a/core/modules/media/src/Entity/Media.php b/core/modules/media/src/Entity/Media.php index 49f7283..2c6862f 100644 --- a/core/modules/media/src/Entity/Media.php +++ b/core/modules/media/src/Entity/Media.php @@ -39,6 +39,7 @@ * "edit" = "Drupal\media\MediaForm", * "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm", * }, + * "add_inline" = "Drupal\media\Form\MediaInlineForm", * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\media\MediaViewsData", * "route_provider" = { diff --git a/core/modules/media/src/Form/MediaInlineForm.php b/core/modules/media/src/Form/MediaInlineForm.php new file mode 100644 index 0000000..8ac4d8c --- /dev/null +++ b/core/modules/media/src/Form/MediaInlineForm.php @@ -0,0 +1,136 @@ +entityFieldManager = $entity_field_manager; + $this->entityTypeManager = $entity_type_manager; + $this->moduleHandler = $module_handler; + $this->entityType = $entity_type; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $container->get('entity_field.manager'), + $container->get('entity_type.manager'), + $container->get('module_handler'), + $entity_type + ); + } + + /** + * {@inheritdoc} + */ + public function entityForm(array $entity_form, FormStateInterface $form_state) { + // Build the media entity form. + // @TODO Why shouldn't we assume this is a MediaInterface instead? + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = $entity_form['#entity']; + $form_display = EntityFormDisplay::collectRenderDisplay($entity, $entity_form['#form_mode']); + $form_display->buildForm($entity, $entity_form, $form_state); + $entity_form['#weight'] = 100; + + // In the service of keeping this form as user-friendly as possible in the + // context of a parent entity form, only show required fields. + // @TODO Inject the service. + /** @var \Drupal\Core\Entity\EntityFieldManager $entity_field_manager */ + $entity_field_manager = \Drupal::service('entity_field.manager'); + $field_definitions = $entity_field_manager->getFieldDefinitions('media', 'file'); + /** @var \Drupal\Core\Field\BaseFieldDefinition $field_definition */ + foreach ($field_definitions as $field_definition) { + $field_name = $field_definition->getName(); + if (!$field_definition->isRequired()) { + $entity_form[$field_name]['#access'] = FALSE; + } + else { + // @TODO: Why is this here? + $entity_form[$field_name]['#element_validate'] = []; + } + + } + + // We've already set the just-uploaded file as the value of the source + // field, so hide that too. + $source_field = $entity->getSource() + ->getSourceFieldDefinition($entity->bundle->entity) + ->getName(); + $entity_form[$source_field]['#access'] = FALSE; + + // Inline entities inherit the parent language, so hide translation-related + // fields as well. + $langcode_key = $this->entityType->getKey('langcode'); + if ($langcode_key && isset($entity_form[$langcode_key])) { + $entity_form[$langcode_key]['#access'] = FALSE; + } + if (!empty($entity_form['#translating'])) { + // Hide the non-translatable fields. + foreach ($entity->getFieldDefinitions() as $field_name => $definition) { + if (isset($entity_form[$field_name]) && $field_name != $langcode_key) { + $entity_form[$field_name]['#access'] = $definition->isTranslatable(); + } + } + } + + + return $entity_form; + } + +} diff --git a/core/modules/media/src/MediaInlineFormInterface.php b/core/modules/media/src/MediaInlineFormInterface.php new file mode 100644 index 0000000..d624e66 --- /dev/null +++ b/core/modules/media/src/MediaInlineFormInterface.php @@ -0,0 +1,28 @@ +getValues(). + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state of the parent form. + */ + public function entityForm(array $entity_form, FormStateInterface $form_state); + +} diff --git a/core/modules/media/src/Plugin/Field/FieldWidget/MediaFileWidget.php b/core/modules/media/src/Plugin/Field/FieldWidget/MediaFileWidget.php new file mode 100644 index 0000000..e103467 --- /dev/null +++ b/core/modules/media/src/Plugin/Field/FieldWidget/MediaFileWidget.php @@ -0,0 +1,327 @@ +getFieldSettings(); + + // The field settings include defaults for the field type. However, this + // widget is a base class for other widgets (e.g., ImageWidget) that may act + // on field types without these expected settings. + $field_settings += [ + 'display_default' => NULL, + 'display_field' => NULL, + 'description_field' => NULL, + ]; + + $defaults = [ + 'mids' => [], + 'fids' => [], + 'display' => (bool) $field_settings['display_default'], + 'description' => '', + ]; + + $target_bundle = array_shift($this->fieldDefinition->getSetting('handler_settings')['target_bundles']); + /** @var \Drupal\media\Entity\MediaType $media_type */ + $media_type = MediaType::load($target_bundle); + $source = $media_type->getSource(); + $source_data_definition = FieldItemDataDefinition::create($source->getSourceFieldDefinition($media_type)); + $file_item = new FileItem($source_data_definition); + + $element_info = $this->elementInfo->getInfo('media_managed_file'); + $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(); + + $element += [ + '#type' => 'media_managed_file', + '#upload_location' => $file_item->getUploadLocation(), + '#upload_validators' => $file_item->getUploadValidators(), + '#value_callback' => [get_class($this), 'value'], + '#process' => array_merge($element_info['#process'], [[get_class($this), 'process']]), + '#progress_indicator' => $this->getSetting('progress_indicator'), + // Allows this field to return an array instead of a single value. + '#extended' => TRUE, + // Add properties needed by value() and process() methods. + '#field_name' => $this->fieldDefinition->getName(), + // This is actually talking about the entity type ON WHICH this field is + // placed, not the entity type TO WHICH it can refer. In fact, it seems + // only to be used, unnecessarily, on FileWidget::validateMultipleCount(). + '#entity_type' => $items->getEntity()->getEntityTypeId(), + '#display_field' => (bool) $field_settings['display_field'], + '#display_default' => $field_settings['display_default'], + '#description_field' => $field_settings['description_field'], + '#cardinality' => $cardinality, + ]; + + $element['#weight'] = $delta; + + // Save mid, the target_id value that this field ultimately cares about. + if (!isset($items[$delta]->mids) && isset($items[$delta]->target_id)) { + $items[$delta]->mids = [$items[$delta]->target_id]; + } + $element['#default_value'] = $items[$delta]->getValue() + $defaults; + + $default_fids = $element['#extended'] ? $element['#default_value']['fids'] : $element['#default_value']; + if (empty($default_fids)) { + $file_upload_help = [ + '#theme' => 'file_upload_help', + '#description' => $element['#description'], + '#upload_validators' => $element['#upload_validators'], + '#cardinality' => $cardinality, + ]; + // @TODO: Inject the service. + $element['#description'] = \Drupal::service('renderer')->renderPlain($file_upload_help); + $element['#multiple'] = $cardinality != 1 ? TRUE : FALSE; + if ($cardinality != 1 && $cardinality != -1) { + $element['#element_validate'][] = [get_class($this), 'validateMultipleCount']; + } + } + + return $element; + } + + /** + * {@inheritdoc} + */ + public function massageFormValues(array $values, array $form, FormStateInterface $form_state) { + // Override FileWidget::massageFormValues() in order to reference Media + // entities instead of files. + $new_values = []; + foreach ($values as &$value) { + foreach ($value['mids'] as $mid) { + $new_value = $value; + $new_value['target_id'] = $mid; + unset($new_value['mids']); + $new_values[] = $new_value; + } + } + + return $new_values; + } + + /** + * // @TODO update the docblock, the inherited one doesn't make sense anymore. + */ + public static function value($element, $input, FormStateInterface $form_state) { + $return = parent::value($element, $input, $form_state); + + // When the form has just been submitted with file(s) being uploaded, + // $input['mids'] should be empty. FileWidget::value() calls + // ManagedFile::valueCallback(), which populates $return['fids']. If there's + // info in there AND we're not doing the subsequent form rebuild, it's time + // to make the media. + if (empty($input['mids']) && !empty($return['fids']) && !$form_state->isRebuilding()) { + foreach ($return['fids'] as $fid) { + $file = File::load($fid); + /** @var \Drupal\media\MediaInterface $media_entity */ + $media_entity = Media::create([ + // @TODO: Shouldn't media entities automatically deal with empty names? + 'name' => $file->getFilename(), + 'bundle' => 'file', // TODO: generalize (note that we have to apply this to MediaInlineFileWidgetTest::setUp() too, in createdMediaType() call) + // @TODO: Inject the service. + 'uid' => \Drupal::currentUser()->id(), + // TODO: figure out langcode in this context. + // 'langcode' => !empty($element['#langcode']) ? $element['#langcode'] : LanguageInterface::LANGCODE_DEFAULT, + ]); + $media_entity->setPublished(); + $source_field = $media_entity->getSource() + ->getSourceFieldDefinition($media_entity->bundle->entity) + ->getName(); + $media_entity->set($source_field, $fid); + // @TODO: Figure out how to handle (if we want to handle it) what to do + // with media entities created by an "abandoned" upload (e.g. when the + // user uploads the file, then does not save the host form). + $media_entity->save(); + $return['mids'][] = $media_entity->id(); + } + } + elseif (!empty($input['mids'])) { + $mid = $input['mids']; + $media_fields_key = 'media_' . $mid; + + if (!empty($input[$media_fields_key])) { + $media_entity = Media::load($mid); + foreach ($input[$media_fields_key] as $field_name => $field_value) { + $media_entity->set($field_name, $field_value); + } + $media_entity->save(); + } + else { + // Warn process() that this is a preexisting value. + $return['media_already_saved'] = TRUE; + } + $return['mids'] = [$mid]; + } + + return $return; + } + + /** + * // @TODO update the docblock, the inherited one doesn't make sense anymore. + */ + public static function process($element, FormStateInterface $form_state, $form) { + $element = parent::process($element, $form_state, $form); + $mid = FALSE; + $show_form = FALSE; + + if (!empty($element['#value']['target_id'])) { + // This is an existing entity ref and we're loading the edit page the + // first time. Just show the icon and name. + $mid = $element['#value']['target_id']; + } + elseif (!empty($element['#value']['mids']) && count($element['#value']['mids']) === 1) { + // If $element['mids']['#value'] has multiple values, that means this is + // the first pass after someone uploaded several files; ignore. If it has + // only ONE, then this media entity was just created and we want to force + // the user to fill out all the new entity's required fields. I.e., show + // the form, as well as the standard icon and name. + $mid = $element['#value']['mids'][0]; + + if (empty($element['#value']['media_already_saved'])) { + $show_form = TRUE; + } + } + + if ($mid) { + // First, get rid of the file link that FileManaged has created, and + // replace with a media-entity link. + $fid = $element['#value']['fids'][0]; + unset($element['file_' . $fid]); + // Under some circumstances this button said "remove selected," but since + // we just removed the checkbox this button should instead always just say + // "remove." + $element['remove_button']['#value'] = t('Remove'); + + // Now show the media icon and name. + $form_element_name = 'media_' . $mid; + $form_element_parents = array_merge($element['#parents'], [$form_element_name]); + $media_entity = Media::load($mid); + + $element[$form_element_name]['media_icon'] = [ + '#theme' => 'image_style', + '#style_name' => 'thumbnail', + '#uri' => $media_entity->getSource()->getMetadata($media_entity, 'thumbnail_uri'), + '#weight' => -20, + ]; + $link_build = Link::createFromRoute($media_entity->getName(), 'entity.media.canonical', ['media' => $mid])->toRenderable(); + $element[$form_element_name]['media_name'] = $link_build + ['#weight' => -10]; + + if ($show_form) { + // Create and add the media entity form. + $form_mode = 'add_inline'; + $media_form = [ + '#entity' => $media_entity, + '#form_mode' => $form_mode, + '#parents' => $form_element_parents, + ]; + // @TODO: Inject the service. + $inline_form_handler = \Drupal::entityTypeManager() + ->getHandler('media', $form_mode); + $media_form = $inline_form_handler->entityForm($media_form, $form_state); + + // @TODO Investigate the possibility of wrapping the media form elements + // in a container, to avoid inadvertently overrides during this merge. + $element[$form_element_name] += $media_form; + } + } + + return $element; + } + + /** + * Form submission handler for upload/remove button of formElement(). + * + * Overrides FileWidget::submit(), performing the exact same functionality, + * only maintaining awareness of mids as well as fids. + * + * @see \Drupal\file\Plugin\Field\FieldWidget\FileWidget::submit() + * @see file_managed_file_submit() + */ + public static function submit($form, FormStateInterface $form_state) { + // During the form rebuild, formElement() will create field item widget + // elements using re-indexed deltas, so clear out FormState::$input to + // avoid a mismatch between old and new deltas. The rebuilt elements will + // have #default_value set appropriately for the current state of the field, + // so nothing is lost in doing this. + $button = $form_state->getTriggeringElement(); + $parents = array_slice($button['#parents'], 0, -2); + NestedArray::setValue($form_state->getUserInput(), $parents, NULL); + + // Go one level up in the form, to the widgets container. + $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1)); + $field_name = $element['#field_name']; + $parents = $element['#field_parents']; + + $submitted_values = NestedArray::getValue($form_state->getValues(), array_slice($button['#parents'], 0, -2)); + foreach ($submitted_values as $delta => $submitted_value) { + if (empty($submitted_value['fids'])) { + unset($submitted_values[$delta]); + } + } + + // If there are more files uploaded via the same widget, we have to separate + // them, as we display each file in its own widget. + $new_values = []; + foreach ($submitted_values as $submitted_value) { + if (is_array($submitted_value['fids']) && is_array($submitted_value['mids'])) { + foreach ($submitted_value['fids'] as $delta => $fid) { + $new_value = $submitted_value; + $new_value['fids'] = [$fid]; + $new_value['mids'] = [$submitted_value['mids'][$delta]]; + $new_values[] = $new_value; + } + } + } + + // Re-index deltas after removing empty items. + $submitted_values = array_values($new_values); + + // Update form_state values. + NestedArray::setValue($form_state->getValues(), array_slice($button['#parents'], 0, -2), $submitted_values); + + // Update items. + $field_state = static::getWidgetState($parents, $field_name, $form_state); + $field_state['items'] = $submitted_values; + static::setWidgetState($parents, $field_name, $form_state, $field_state); + } + + /** + * {@inheritdoc} + */ + public static function isApplicable(FieldDefinitionInterface $field_definition) { + return parent::isApplicable($field_definition); + // @TODO Restrict this to only fields targeting a single media type. + } +} diff --git a/core/modules/media/tests/fixtures/example_1.pdf b/core/modules/media/tests/fixtures/example_1.pdf new file mode 100644 index 0000000..e69de29 diff --git a/core/modules/media/tests/fixtures/example_2.pdf b/core/modules/media/tests/fixtures/example_2.pdf new file mode 100644 index 0000000..e69de29 diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaInlineFileWidgetTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaInlineFileWidgetTest.php new file mode 100644 index 0000000..1e448e2 --- /dev/null +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaInlineFileWidgetTest.php @@ -0,0 +1,253 @@ +createMediaType(['bundle' => 'file'], 'file'); + $this->mediaTypeId = $media_type->id(); + // Create two test fields on this media type, one of them required. + $media_required_field_storage = FieldStorageConfig::create([ + 'type' => 'string', + 'entity_type' => 'media', + 'field_name' => 'field_media_required', + 'settings' => [ + 'max_length' => 255, + ], + ]); + $media_required_field_storage->save(); + FieldConfig::create([ + 'field_storage' => $media_required_field_storage, + 'bundle' => $this->mediaTypeId, + 'required' => TRUE, + ])->save(); + entity_get_form_display('media', $media_type->id(), 'add_inline') + ->setComponent('field_media_required', ['type' => 'string_textfield']) + ->save(); + $media_nonrequired_field_storage = FieldStorageConfig::create([ + 'type' => 'string', + 'entity_type' => 'media', + 'field_name' => 'field_media_nonrequired', + 'settings' => [ + 'max_length' => 255, + ], + ]); + $media_nonrequired_field_storage->save(); + FieldConfig::create([ + 'field_storage' => $media_nonrequired_field_storage, + 'bundle' => $this->mediaTypeId, + 'required' => FALSE, + ])->save(); + // Deliberately add it to the form, to test that even then we don't show + // this field when embedding the form inside the widget. + entity_get_form_display('media', $this->mediaTypeId, 'add_inline') + ->setComponent('field_media_nonrequired', ['type' => 'string_textfield']) + ->save(); + entity_get_display('media', $this->mediaTypeId, 'default') + ->setComponent('field_media_file', [ + 'type' => 'file_default', + 'label' => 'hidden', + 'settings' => [], + ]) + ->setComponent('field_media_required', [ + 'type' => 'string', + 'label' => 'hidden', + 'settings' => [], + ]) + ->setComponent('field_media_nonrequired', [ + 'type' => 'string', + 'label' => 'hidden', + 'settings' => [], + ]) + ->save(); + + $node_type = $this->drupalCreateContentType(); + $this->nodeTypeId = $node_type->id(); + $field_storage = FieldStorageConfig::create([ + 'type' => 'entity_reference', + 'cardinality' => -1, + 'entity_type' => 'node', + 'field_name' => 'field_media_reference', + 'settings' => [ + 'target_type' => 'media', + ], + ]); + $field_storage->save(); + FieldConfig::create([ + 'field_storage' => $field_storage, + 'bundle' => $this->nodeTypeId, + 'settings' => [ + 'handler_settings' => [ + 'target_bundles' => [ + $this->mediaTypeId => $this->mediaTypeId, + ], + ], + ], + ])->save(); + entity_get_form_display('node', $this->nodeTypeId, 'default') + ->setComponent('field_media_reference', ['type' => 'media_file']) + ->save(); + entity_get_display('node', $this->nodeTypeId, 'default') + ->setComponent('field_media_reference', [ + 'type' => 'entity_reference_entity_view', + 'label' => 'hidden', + 'settings' => [ + 'view_mode' => 'full', + ], + ])->save(); + + // Create a couple of files for testing. + for ($i = 0; $i < 2; $i++) { + $test_filename = $this->randomMachineName() . '.txt'; + $test_filepath = 'public://' . $test_filename; + file_put_contents($test_filepath, $this->randomMachineName()); + $this->testFilePaths[$i] = \Drupal::service('file_system')->realpath($test_filepath); + } + + } + + /** + * Tests the file widget behavior. + */ + public function testInlineFileWidget() { + $assert_session = $this->assertSession(); + $page = $this->getSession()->getPage(); + + $this->drupalGet('/node/add/' . $this->nodeTypeId); + + // Ensure the media field is present, but no required fields exist in there. + $media_field = $assert_session->elementExists('css', 'details[data-drupal-selector="edit-field-media-reference"]'); + $assert_session->elementNotExists('css', '.required', $media_field); + + // Upload a file and see that new required fields appeared. + $first_upload_element_id = 'edit-field-media-reference-0-upload'; + $page->attachFileToField($first_upload_element_id, $this->testFilePaths[0]); + $result = $assert_session->waitForButton('Remove'); + $this->assertNotEmpty($result); + $media_field = $assert_session->elementExists('css', 'details[data-drupal-selector="edit-field-media-reference"]'); + // The name field should be present and required. + $assert_session->elementExists('css', '.field--name-name input.required', $media_field); + // The additional field sould be present and required. + $assert_session->elementExists('css', '.field--name-field-media-required input.required', $media_field); + // The non-required field should not be there. + $assert_session->elementNotExists('css', '.field--name-field-media-nonrequired input', $media_field); + + // By now the media entity should already exist, check that. + $filename1 = basename($this->testFilePaths[0]); + $media = $this->container->get('entity_type.manager')->getStorage('media') + ->loadByProperties(['name' => $filename1]); + $media = reset($media); + /** @var \Drupal\media\MediaInterface $media */ + $this->assertTrue($media->isPublished()); + + // Save the node and check page elements correspond to what is expected. + $node_title = 'Host Node 1'; + $page->fillField('Title', $node_title); + $required_text = $this->randomMachineName(); + $page->fillField('field_media_required', $required_text); + $page->pressButton('Save'); + // The node has been correctly saved. + $assert_session->pageTextContains("{$this->nodeTypeId} $node_title has been created"); + // We have a reference media container. + $media_field = $assert_session->elementExists('css', '.field--name-field-media-reference'); + // We have a file link inside that container. + $assert_session->elementExists('css', '.field--name-field-media-file a', $media_field); + // The required field is present. + $required_element = $assert_session->elementExists('css', '.field--name-field-media-required', $media_field); + // The required field text is what we expect. + $this->assertEquals($required_text, $required_element->getText()); + + // Edit the node, add a second file, check the widget supports multiple + // values and creates a second media without messing up. + $node = $this->container->get('entity_type.manager')->getStorage('node') + ->loadByProperties(['title' => $node_title]); + $node = reset($node); + $this->drupalGet("/node/{$node->id()}/edit"); + // First file is still there, and remove button is present. + $assert_session->elementContains('css', '#edit-field-media-reference-table', $filename1); + $media_field = $assert_session->elementExists('css', 'details[data-drupal-selector="edit-field-media-reference"]'); + $remove_button = $media_field->findButton('Remove'); + $this->assertNotNull($remove_button); + $second_upload_element_id = 'edit-field-media-reference-1-upload'; + // Upload the second file. + $filename2 = basename($this->testFilePaths[1]); + $page->attachFileToField($second_upload_element_id, $this->testFilePaths[1]); + $assert_session->assertWaitOnAjaxRequest(); + + // Fill in the second media required field and save. + $required_text2 = $this->randomMachineName(); + $page->fillField('field_media_required', $required_text2); + $page->pressButton('Save'); + // Check everything was saved correctly. + $assert_session->pageTextContains("{$this->nodeTypeId} $node_title has been updated"); + $assert_session->pageTextContains($filename1); + $assert_session->pageTextContains($filename2); + $assert_session->pageTextContains($required_text); + $assert_session->pageTextContains($required_text2); + + // Both media entities should exist now. + foreach ([$filename1, $filename2] as $filename) { + $media = $this->container->get('entity_type.manager')->getStorage('media') + ->loadByProperties(['name' => $filename]); + $media = reset($media); + /** @var \Drupal\media\MediaInterface $media */ + $this->assertTrue($media->isPublished()); + } + + // Edit the node, verify we can successfuly remove one of the items. + $this->drupalGet("/node/{$node->id()}/edit"); + $assert_session->pageTextContains($filename1); + $assert_session->pageTextContains($filename2); + $assert_session->buttonExists('edit-field-media-reference-0-remove-button'); + $page->pressButton('edit-field-media-reference-0-remove-button'); + $assert_session->assertWaitOnAjaxRequest(); + $assert_session->pageTextNotContains($filename1); + $assert_session->pageTextContains($filename2); + $page->pressButton('Save'); + $assert_session->pageTextContains("{$this->nodeTypeId} $node_title has been updated"); + $assert_session->pageTextNotContains($filename1); + $assert_session->pageTextContains($filename2); + $assert_session->pageTextNotContains($required_text); + $assert_session->pageTextContains($required_text2); + } + +}