Chris@0: fieldStorageDefinition = [ Chris@17: 'field_name' => mb_strtolower($this->randomMachineName()), Chris@0: 'entity_type' => 'entity_test', Chris@0: 'type' => 'test_field', Chris@0: ]; Chris@0: $this->fieldStorage = FieldStorageConfig::create($this->fieldStorageDefinition); Chris@0: $this->fieldStorage->save(); Chris@0: $this->fieldDefinition = [ Chris@0: 'field_name' => $this->fieldStorage->getName(), Chris@0: 'entity_type' => 'entity_test', Chris@0: 'bundle' => 'entity_test', Chris@0: ]; Chris@0: } Chris@0: Chris@0: // TODO : test creation with Chris@0: // - a full fledged $field structure, check that all the values are there Chris@0: // - a minimal $field structure, check all default values are set Chris@0: // defer actual $field comparison to a helper function, used for the two cases above, Chris@0: // and for testUpdateField Chris@0: Chris@0: /** Chris@0: * Test the creation of a field. Chris@0: */ Chris@0: public function testCreateField() { Chris@0: $field = FieldConfig::create($this->fieldDefinition); Chris@0: $field->save(); Chris@0: Chris@0: $field = FieldConfig::load($field->id()); Chris@0: $this->assertTrue($field->getSetting('field_setting_from_config_data')); Chris@0: $this->assertNull($field->getSetting('config_data_from_field_setting')); Chris@0: Chris@0: // Read the configuration. Check against raw configuration data rather than Chris@0: // the loaded ConfigEntity, to be sure we check that the defaults are Chris@0: // applied on write. Chris@0: $config = $this->config('field.field.' . $field->id())->get(); Chris@0: $field_type_manager = \Drupal::service('plugin.manager.field.field_type'); Chris@0: Chris@0: $this->assertTrue($config['settings']['config_data_from_field_setting']); Chris@0: $this->assertTrue(!isset($config['settings']['field_setting_from_config_data'])); Chris@0: Chris@0: // Since we are working with raw configuration, this needs to be unset Chris@0: // manually. Chris@0: // @see Drupal\field_test\Plugin\Field\FieldType\TestItem::fieldSettingsFromConfigData() Chris@0: unset($config['settings']['config_data_from_field_setting']); Chris@0: Chris@0: // Check that default values are set. Chris@0: $this->assertEqual($config['required'], FALSE, 'Required defaults to false.'); Chris@0: $this->assertIdentical($config['label'], $this->fieldDefinition['field_name'], 'Label defaults to field name.'); Chris@0: $this->assertIdentical($config['description'], '', 'Description defaults to empty string.'); Chris@0: Chris@0: // Check that default settings are set. Chris@0: $this->assertEqual($config['settings'], $field_type_manager->getDefaultFieldSettings($this->fieldStorageDefinition['type']), 'Default field settings have been written.'); Chris@0: Chris@0: // Check that the denormalized 'field_type' was properly written. Chris@0: $this->assertEqual($config['field_type'], $this->fieldStorageDefinition['type']); Chris@0: Chris@0: // Guarantee that the field/bundle combination is unique. Chris@0: try { Chris@0: FieldConfig::create($this->fieldDefinition)->save(); Chris@0: $this->fail(t('Cannot create two fields with the same field / bundle combination.')); Chris@0: } Chris@0: catch (EntityStorageException $e) { Chris@0: $this->pass(t('Cannot create two fields with the same field / bundle combination.')); Chris@0: } Chris@0: Chris@0: // Check that the specified field exists. Chris@0: try { Chris@0: $this->fieldDefinition['field_name'] = $this->randomMachineName(); Chris@0: FieldConfig::create($this->fieldDefinition)->save(); Chris@0: $this->fail(t('Cannot create a field with a non-existing storage.')); Chris@0: } Chris@0: catch (FieldException $e) { Chris@0: $this->pass(t('Cannot create a field with a non-existing storage.')); Chris@0: } Chris@0: Chris@0: // TODO: test other failures. Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests setting and adding property constraints to a configurable field. Chris@0: * Chris@0: * @covers ::setPropertyConstraints Chris@0: * @covers ::addPropertyConstraints Chris@0: */ Chris@0: public function testFieldPropertyConstraints() { Chris@0: $field = FieldConfig::create($this->fieldDefinition); Chris@0: $field->save(); Chris@0: $field_name = $this->fieldStorage->getName(); Chris@0: Chris@0: // Test that constraints are applied to configurable fields. A TestField and Chris@0: // a Range constraint are added dynamically to limit the field to values Chris@0: // between 0 and 32. Chris@0: // @see field_test_entity_bundle_field_info_alter() Chris@0: \Drupal::state()->set('field_test_constraint', $field_name); Chris@0: Chris@0: // Clear the field definitions cache so the new constraints added by Chris@0: // field_test_entity_bundle_field_info_alter() are taken into consideration. Chris@0: \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions(); Chris@0: Chris@0: // Test the newly added property constraints in the same request as when the Chris@0: // caches were cleared. This will test the field definitions that are stored Chris@0: // in the static cache of Chris@0: // \Drupal\Core\Entity\EntityFieldManager::getFieldDefinitions(). Chris@0: $this->doFieldPropertyConstraintsTests(); Chris@0: Chris@0: // In order to test a real-world scenario where the property constraints are Chris@0: // only stored in the persistent cache of Chris@0: // \Drupal\Core\Entity\EntityFieldManager::getFieldDefinitions(), we need to Chris@0: // simulate a new request by removing the 'entity_field.manager' service, Chris@0: // thus forcing it to be re-initialized without static caches. Chris@0: \Drupal::getContainer()->set('entity_field.manager', NULL); Chris@0: Chris@0: // This will test the field definitions that are stored in the persistent Chris@0: // cache by \Drupal\Core\Entity\EntityFieldManager::getFieldDefinitions(). Chris@0: $this->doFieldPropertyConstraintsTests(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests configurable field validation. Chris@0: * Chris@0: * @see field_test_entity_bundle_field_info_alter() Chris@0: */ Chris@0: protected function doFieldPropertyConstraintsTests() { Chris@0: $field_name = $this->fieldStorage->getName(); Chris@0: Chris@0: // Check that a valid value (not -2 and between 0 and 32) doesn't trigger Chris@0: // any violation. Chris@0: $entity = EntityTest::create(); Chris@0: $entity->set($field_name, 1); Chris@0: $violations = $entity->validate(); Chris@0: $this->assertCount(0, $violations, 'No violations found when in-range value passed.'); Chris@0: Chris@0: // Check that a value that is specifically restricted triggers both Chris@0: // violations. Chris@0: $entity->set($field_name, -2); Chris@0: $violations = $entity->validate(); Chris@0: $this->assertCount(2, $violations, 'Two violations found when using a null and outside the range value.'); Chris@0: Chris@0: $this->assertEquals($field_name . '.0.value', $violations[0]->getPropertyPath()); Chris@0: $this->assertEquals(t('%name does not accept the value @value.', ['%name' => $field_name, '@value' => -2]), $violations[0]->getMessage()); Chris@0: Chris@0: $this->assertEquals($field_name . '.0.value', $violations[1]->getPropertyPath()); Chris@0: $this->assertEquals(t('This value should be %limit or more.', ['%limit' => 0]), $violations[1]->getMessage()); Chris@0: Chris@0: // Check that a value that is not specifically restricted but outside the Chris@0: // range triggers the expected violation. Chris@0: $entity->set($field_name, 33); Chris@0: $violations = $entity->validate(); Chris@0: $this->assertCount(1, $violations, 'Violations found when using value outside the range.'); Chris@0: $this->assertEquals($field_name . '.0.value', $violations[0]->getPropertyPath()); Chris@0: $this->assertEquals(t('This value should be %limit or less.', ['%limit' => 32]), $violations[0]->getMessage()); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Test creating a field with custom storage set. Chris@0: */ Chris@0: public function testCreateFieldCustomStorage() { Chris@17: $field_name = mb_strtolower($this->randomMachineName()); Chris@0: \Drupal::state()->set('field_test_custom_storage', $field_name); Chris@0: Chris@0: $field_storage = FieldStorageConfig::create([ Chris@0: 'field_name' => $field_name, Chris@0: 'entity_type' => 'entity_test', Chris@0: 'type' => 'test_field', Chris@0: 'custom_storage' => TRUE, Chris@0: ]); Chris@0: $field_storage->save(); Chris@0: Chris@0: $field = FieldConfig::create([ Chris@0: 'field_name' => $field_storage->getName(), Chris@0: 'entity_type' => 'entity_test', Chris@0: 'bundle' => 'entity_test', Chris@0: ]); Chris@0: $field->save(); Chris@0: Chris@0: \Drupal::entityManager()->clearCachedFieldDefinitions(); Chris@0: Chris@0: // Check that no table has been created for the field. Chris@0: $this->assertFalse(\Drupal::database()->schema()->tableExists('entity_test__' . $field_storage->getName())); Chris@0: Chris@0: // Save an entity with a value in the custom storage field and verify no Chris@0: // data is retrieved on load. Chris@0: $entity = EntityTest::create(['name' => $this->randomString(), $field_name => 'Test value']); Chris@0: $this->assertIdentical('Test value', $entity->{$field_name}->value, 'The test value is set on the field.'); Chris@0: Chris@0: $entity->save(); Chris@0: $entity = EntityTest::load($entity->id()); Chris@0: Chris@0: $this->assertNull($entity->{$field_name}->value, 'The loaded entity field value is NULL.'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Test reading back a field definition. Chris@0: */ Chris@0: public function testReadField() { Chris@0: FieldConfig::create($this->fieldDefinition)->save(); Chris@0: Chris@0: // Read the field back. Chris@0: $field = FieldConfig::load('entity_test.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']); Chris@0: $this->assertTrue($this->fieldDefinition['field_name'] == $field->getName(), 'The field was properly read.'); Chris@0: $this->assertTrue($this->fieldDefinition['entity_type'] == $field->getTargetEntityTypeId(), 'The field was properly read.'); Chris@0: $this->assertTrue($this->fieldDefinition['bundle'] == $field->getTargetBundle(), 'The field was properly read.'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Test the update of a field. Chris@0: */ Chris@0: public function testUpdateField() { Chris@0: FieldConfig::create($this->fieldDefinition)->save(); Chris@0: Chris@0: // Check that basic changes are saved. Chris@0: $field = FieldConfig::load('entity_test.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']); Chris@0: $field->setRequired(!$field->isRequired()); Chris@0: $field->setLabel($this->randomMachineName()); Chris@0: $field->set('description', $this->randomMachineName()); Chris@0: $field->setSetting('test_field_setting', $this->randomMachineName()); Chris@0: $field->save(); Chris@0: Chris@0: $field_new = FieldConfig::load('entity_test.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']); Chris@0: $this->assertEqual($field->isRequired(), $field_new->isRequired(), '"required" change is saved'); Chris@0: $this->assertEqual($field->getLabel(), $field_new->getLabel(), '"label" change is saved'); Chris@0: $this->assertEqual($field->getDescription(), $field_new->getDescription(), '"description" change is saved'); Chris@0: Chris@0: // TODO: test failures. Chris@0: } Chris@0: Chris@0: /** Chris@0: * Test the deletion of a field with no data. Chris@0: */ Chris@0: public function testDeleteFieldNoData() { Chris@0: // Deleting and purging fields with data is tested in Chris@0: // \Drupal\Tests\field\Kernel\BulkDeleteTest. Chris@0: Chris@0: // Create two fields for the same field storage so we can test that only one Chris@0: // is deleted. Chris@0: FieldConfig::create($this->fieldDefinition)->save(); Chris@0: $another_field_definition = $this->fieldDefinition; Chris@0: $another_field_definition['bundle'] .= '_another_bundle'; Chris@0: entity_test_create_bundle($another_field_definition['bundle']); Chris@0: FieldConfig::create($another_field_definition)->save(); Chris@0: Chris@0: // Test that the first field is not deleted, and then delete it. Chris@0: $field = current(entity_load_multiple_by_properties('field_config', ['entity_type' => 'entity_test', 'field_name' => $this->fieldDefinition['field_name'], 'bundle' => $this->fieldDefinition['bundle'], 'include_deleted' => TRUE])); Chris@0: $this->assertTrue(!empty($field) && empty($field->deleted), 'A new field is not marked for deletion.'); Chris@0: $field->delete(); Chris@0: Chris@0: // Make sure the field was deleted without being marked for purging as there Chris@0: // was no data. Chris@0: $fields = entity_load_multiple_by_properties('field_config', ['entity_type' => 'entity_test', 'field_name' => $this->fieldDefinition['field_name'], 'bundle' => $this->fieldDefinition['bundle'], 'include_deleted' => TRUE]); Chris@0: $this->assertEquals(0, count($fields), 'A deleted field is marked for deletion.'); Chris@0: Chris@0: // Try to load the field normally and make sure it does not show up. Chris@0: $field = FieldConfig::load('entity_test.' . '.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']); Chris@0: $this->assertTrue(empty($field), 'Field was deleted'); Chris@0: Chris@0: // Make sure the other field is not deleted. Chris@0: $another_field = FieldConfig::load('entity_test.' . $another_field_definition['bundle'] . '.' . $another_field_definition['field_name']); Chris@0: $this->assertTrue(!empty($another_field) && !$another_field->isDeleted(), 'A non-deleted field is not marked for deletion.'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests the cross deletion behavior between field storages and fields. Chris@0: */ Chris@0: public function testDeleteFieldCrossDeletion() { Chris@0: $field_definition_2 = $this->fieldDefinition; Chris@0: $field_definition_2['bundle'] .= '_another_bundle'; Chris@0: entity_test_create_bundle($field_definition_2['bundle']); Chris@0: Chris@0: // Check that deletion of a field storage deletes its fields. Chris@0: $field_storage = $this->fieldStorage; Chris@0: FieldConfig::create($this->fieldDefinition)->save(); Chris@0: FieldConfig::create($field_definition_2)->save(); Chris@0: $field_storage->delete(); Chris@0: $this->assertFalse(FieldConfig::loadByName('entity_test', $this->fieldDefinition['bundle'], $field_storage->getName())); Chris@0: $this->assertFalse(FieldConfig::loadByName('entity_test', $field_definition_2['bundle'], $field_storage->getName())); Chris@0: Chris@0: // Check that deletion of the last field deletes the storage. Chris@0: $field_storage = FieldStorageConfig::create($this->fieldStorageDefinition); Chris@0: $field_storage->save(); Chris@0: $field = FieldConfig::create($this->fieldDefinition); Chris@0: $field->save(); Chris@0: $field_2 = FieldConfig::create($field_definition_2); Chris@0: $field_2->save(); Chris@0: $field->delete(); Chris@0: $this->assertTrue(FieldStorageConfig::loadByName('entity_test', $field_storage->getName())); Chris@0: $field_2->delete(); Chris@0: $this->assertFalse(FieldStorageConfig::loadByName('entity_test', $field_storage->getName())); Chris@0: Chris@0: // Check that deletion of all fields using a storage simultaneously deletes Chris@0: // the storage. Chris@0: $field_storage = FieldStorageConfig::create($this->fieldStorageDefinition); Chris@0: $field_storage->save(); Chris@0: $field = FieldConfig::create($this->fieldDefinition); Chris@0: $field->save(); Chris@0: $field_2 = FieldConfig::create($field_definition_2); Chris@0: $field_2->save(); Chris@0: $this->container->get('entity.manager')->getStorage('field_config')->delete([$field, $field_2]); Chris@0: $this->assertFalse(FieldStorageConfig::loadByName('entity_test', $field_storage->getName())); Chris@0: } Chris@0: Chris@0: }