2009-02-05 03:42:58 +00:00
<?php
// $Id$
2009-04-29 21:33:00 +00:00
/**
* @file
* Unit test file for fields in core.
*/
/**
2009-08-26 02:59:22 +00:00
* Parent class for Field API tests.
2009-04-29 21:33:00 +00:00
*/
2009-08-26 02:59:22 +00:00
class FieldTestCase extends DrupalWebTestCase {
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
var $default_storage = 'field_sql_storage';
/**
* Set the default field storage backend for fields created during tests.
*/
function setUp() {
// Call parent::setUp().
$args = func_get_args();
call_user_func_array(array('parent', 'setUp'), $args);
// Set default storage backend.
variable_set('field_storage_default', $this->default_storage);
}
2009-08-26 02:59:22 +00:00
/**
* Generate random values for a field_test field.
*
* @param $cardinality
* Number of values to generate.
* @return
* An array of random values, in the format expected for field values.
*/
function _generateTestFieldValues($cardinality) {
$values = array();
for ($i = 0; $i < $cardinality; $i++) {
// field_test fields treat 0 as 'empty value'.
$values[$i]['value'] = mt_rand(1, 127);
}
return $values;
2009-02-05 03:42:58 +00:00
}
2009-08-26 02:59:22 +00:00
}
2009-02-05 03:42:58 +00:00
2009-08-26 02:59:22 +00:00
class FieldAttachTestCase extends FieldTestCase {
2009-02-05 03:42:58 +00:00
function setUp() {
2009-04-29 21:33:00 +00:00
parent::setUp('field_test');
2009-02-05 03:42:58 +00:00
2009-04-29 21:33:00 +00:00
$this->field_name = drupal_strtolower($this->randomName() . '_field_name');
2009-02-05 03:42:58 +00:00
$this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4);
2009-07-07 09:28:07 +00:00
$this->field = field_create_field($this->field);
$this->field_id = $this->field['id'];
2009-02-05 03:42:58 +00:00
$this->instance = array(
'field_name' => $this->field_name,
'bundle' => 'test_bundle',
2009-04-29 21:33:00 +00:00
'label' => $this->randomName() . '_label',
'description' => $this->randomName() . '_description',
2009-02-05 03:42:58 +00:00
'weight' => mt_rand(0, 127),
'settings' => array(
'test_instance_setting' => $this->randomName(),
),
'widget' => array(
'type' => 'test_field_widget',
'label' => 'Test Field',
'settings' => array(
'test_widget_setting' => $this->randomName(),
)
)
);
field_create_instance($this->instance);
}
2009-08-26 02:59:22 +00:00
}
/**
* Unit test class for storage-related field_attach_* functions.
*
* All field_attach_* test work with all field_storage plugins and
* all hook_field_attach_pre_{load,insert,update}() hooks.
*/
class FieldAttachStorageTestCase extends FieldAttachTestCase {
public static function getInfo() {
return array(
'name' => 'Field attach tests (storage-related)',
'description' => 'Test storage-related Field Attach API functions.',
'group' => 'Field',
);
}
2009-02-05 03:42:58 +00:00
2009-04-29 21:33:00 +00:00
/**
2009-04-30 15:17:13 +00:00
* Check field values insert, update and load.
2009-04-29 21:33:00 +00:00
*
2009-04-30 15:17:13 +00:00
* Works independently of the underlying field storage backend. Inserts or
* updates random field data and then loads and verifies the data.
2009-04-29 21:33:00 +00:00
*/
2009-04-30 15:17:13 +00:00
function testFieldAttachSaveLoad() {
2009-05-17 00:32:29 +00:00
// Configure the instance so that we test hook_field_load() (see
// field_test_field_load() in field_test.module).
$this->instance['settings']['test_hook_field_load'] = TRUE;
field_update_instance($this->instance);
2009-08-22 00:58:55 +00:00
$langcode = FIELD_LANGUAGE_NONE;
2009-05-17 00:32:29 +00:00
2009-02-05 03:42:58 +00:00
$entity_type = 'test_entity';
$values = array();
2009-04-29 21:33:00 +00:00
// TODO : test empty values filtering and "compression" (store consecutive deltas).
2009-04-30 15:17:13 +00:00
// Preparation: create three revisions and store them in $revision array.
2009-04-29 21:33:00 +00:00
for ($revision_id = 0; $revision_id < 3; $revision_id++) {
$revision[$revision_id] = field_test_create_stub_entity(0, $revision_id, $this->instance['bundle']);
2009-05-17 03:12:17 +00:00
// Note: we try to insert one extra value.
$values[$revision_id] = $this->_generateTestFieldValues($this->field['cardinality'] + 1);
2009-04-29 21:33:00 +00:00
$current_revision = $revision_id;
2009-04-30 15:17:13 +00:00
// If this is the first revision do an insert.
2009-04-29 21:33:00 +00:00
if (!$revision_id) {
2009-08-22 00:58:55 +00:00
$revision[$revision_id]->{$this->field_name}[$langcode] = $values[$revision_id];
2009-04-29 21:33:00 +00:00
field_attach_insert($entity_type, $revision[$revision_id]);
2009-02-05 03:42:58 +00:00
}
else {
2009-04-30 15:17:13 +00:00
// Otherwise do an update.
2009-08-22 00:58:55 +00:00
$revision[$revision_id]->{$this->field_name}[$langcode] = $values[$revision_id];
2009-04-29 21:33:00 +00:00
field_attach_update($entity_type, $revision[$revision_id]);
2009-02-05 03:42:58 +00:00
}
}
2009-04-30 15:17:13 +00:00
// Confirm current revision loads the correct data.
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
field_attach_load($entity_type, array(0 => $entity));
2009-04-29 21:33:00 +00:00
// Number of values per field loaded equals the field cardinality.
2009-08-22 00:58:55 +00:00
$this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], t('Current revision: expected number of values'));
2009-04-29 21:33:00 +00:00
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
// The field value loaded matches the one inserted or updated.
2009-08-22 00:58:55 +00:00
$this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['value'] , $values[$current_revision][$delta]['value'], t('Current revision: expected value %delta was found.', array('%delta' => $delta)));
2009-05-17 00:32:29 +00:00
// The value added in hook_field_load() is found.
2009-08-22 00:58:55 +00:00
$this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['additional_key'], 'additional_value', t('Current revision: extra information for value %delta was found', array('%delta' => $delta)));
2009-04-29 21:33:00 +00:00
}
// Confirm each revision loads the correct data.
foreach (array_keys($revision) as $revision_id) {
2009-04-30 15:17:13 +00:00
$entity = field_test_create_stub_entity(0, $revision_id, $this->instance['bundle']);
field_attach_load_revision($entity_type, array(0 => $entity));
2009-04-29 21:33:00 +00:00
// Number of values per field loaded equals the field cardinality.
2009-08-22 00:58:55 +00:00
$this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], t('Revision %revision_id: expected number of values.', array('%revision_id' => $revision_id)));
2009-04-29 21:33:00 +00:00
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
2009-04-30 15:17:13 +00:00
// The field value loaded matches the one inserted or updated.
2009-08-22 00:58:55 +00:00
$this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['value'], $values[$revision_id][$delta]['value'], t('Revision %revision_id: expected value %delta was found.', array('%revision_id' => $revision_id, '%delta' => $delta)));
2009-05-17 00:32:29 +00:00
// The value added in hook_field_load() is found.
2009-08-22 00:58:55 +00:00
$this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['additional_key'], 'additional_value', t('Revision %revision_id: extra information for value %delta was found', array('%revision_id' => $revision_id, '%delta' => $delta)));
2009-05-17 00:32:29 +00:00
}
}
}
/**
* Test the 'multiple' load feature.
*/
function testFieldAttachLoadMultiple() {
$entity_type = 'test_entity';
2009-08-22 00:58:55 +00:00
$langcode = FIELD_LANGUAGE_NONE;
2009-05-17 00:32:29 +00:00
// Define 2 bundles.
$bundles = array(
1 => 'test_bundle_1',
2 => 'test_bundle_2',
);
field_test_create_bundle($bundles[1]);
field_test_create_bundle($bundles[2]);
// Define 3 fields:
// - field_1 is in bundle_1 and bundle_2,
// - field_2 is in bundle_1,
// - field_3 is in bundle_2.
$field_bundles_map = array(
1 => array(1, 2),
2 => array(1),
3 => array(2),
);
for ($i = 1; $i <= 3; $i++) {
$field_names[$i] = 'field_' . $i;
$field = array('field_name' => $field_names[$i], 'type' => 'test_field');
2009-08-11 14:59:40 +00:00
$field = field_create_field($field);
$field_ids[$i] = $field['id'];
2009-05-17 00:32:29 +00:00
foreach ($field_bundles_map[$i] as $bundle) {
$instance = array(
'field_name' => $field_names[$i],
'bundle' => $bundles[$bundle],
'settings' => array(
// Configure the instance so that we test hook_field_load()
// (see field_test_field_load() in field_test.module).
'test_hook_field_load' => TRUE,
),
);
field_create_instance($instance);
}
}
// Create one test entity per bundle, with random values.
foreach ($bundles as $index => $bundle) {
$entities[$index] = field_test_create_stub_entity($index, $index, $bundle);
$entity = clone($entities[$index]);
$instances = field_info_instances($bundle);
foreach ($instances as $field_name => $instance) {
$values[$index][$field_name] = mt_rand(1, 127);
2009-08-22 00:58:55 +00:00
$entity->$field_name = array($langcode => array(array('value' => $values[$index][$field_name])));
2009-05-17 00:32:29 +00:00
}
field_attach_insert($entity_type, $entity);
}
// Check that a single load correctly loads field values for both entities.
field_attach_load($entity_type, $entities);
foreach ($entities as $index => $entity) {
$instances = field_info_instances($bundles[$index]);
foreach ($instances as $field_name => $instance) {
// The field value loaded matches the one inserted.
2009-08-22 00:58:55 +00:00
$this->assertEqual($entity->{$field_name}[$langcode][0]['value'], $values[$index][$field_name], t('Entity %index: expected value was found.', array('%index' => $index)));
2009-05-17 00:32:29 +00:00
// The value added in hook_field_load() is found.
2009-08-22 00:58:55 +00:00
$this->assertEqual($entity->{$field_name}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => $index)));
2009-02-05 03:42:58 +00:00
}
}
2009-07-15 17:55:18 +00:00
// Check that the single-field load option works.
$entity = field_test_create_stub_entity(1, 1, $bundles[1]);
2009-08-11 14:59:40 +00:00
field_attach_load($entity_type, array(1 => $entity), FIELD_LOAD_CURRENT, array('field_id' => $field_ids[1]));
2009-08-22 00:58:55 +00:00
$this->assertEqual($entity->{$field_names[1]}[$langcode][0]['value'], $values[1][$field_names[1]], t('Entity %index: expected value was found.', array('%index' => 1)));
$this->assertEqual($entity->{$field_names[1]}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => 1)));
2009-07-15 17:55:18 +00:00
$this->assert(!isset($entity->{$field_names[2]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 2, '%field_name' => $field_names[2])));
$this->assert(!isset($entity->{$field_names[3]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 3, '%field_name' => $field_names[3])));
2009-02-05 03:42:58 +00:00
}
2009-04-30 15:17:13 +00:00
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
/**
* Test saving and loading fields using different storage backends.
*/
function testFieldAttachSaveLoadDifferentStorage() {
$entity_type = 'test_entity';
$langcode = FIELD_LANGUAGE_NONE;
// Create two fields using different storage backends, and their instances.
$fields = array(
array(
'field_name' => 'field_1',
'type' => 'test_field',
'cardinality' => 4,
'storage' => array('type' => 'field_sql_storage')
),
array(
'field_name' => 'field_2',
'type' => 'test_field',
'cardinality' => 4,
'storage' => array('type' => 'field_test_storage')
),
);
foreach ($fields as $field) {
field_create_field($field);
$instance = array(
'field_name' => $field['field_name'],
'bundle' => 'test_bundle',
);
field_create_instance($instance);
}
$entity_init = field_test_create_stub_entity();
// Create entity and insert random values.
$entity = clone($entity_init);
$values = array();
foreach ($fields as $field) {
$values[$field['field_name']] = $this->_generateTestFieldValues($this->field['cardinality']);
$entity->{$field['field_name']}[$langcode] = $values[$field['field_name']];
}
field_attach_insert($entity_type, $entity);
// Check that values are loaded as expected.
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
foreach ($fields as $field) {
$this->assertEqual($values[$field['field_name']], $entity->{$field['field_name']}[$langcode], t('%storage storage: expected values were found.', array('%storage' => $field['storage']['type'])));
}
}
2009-04-29 21:33:00 +00:00
/**
* Tests insert and update with missing or NULL fields.
*/
2009-02-05 03:42:58 +00:00
function testFieldAttachSaveMissingData() {
$entity_type = 'test_entity';
2009-05-01 15:28:13 +00:00
$entity_init = field_test_create_stub_entity();
2009-08-22 00:58:55 +00:00
$langcode = FIELD_LANGUAGE_NONE;
2009-02-05 03:42:58 +00:00
2009-04-29 21:33:00 +00:00
// Insert: Field is missing.
2009-05-01 15:28:13 +00:00
$entity = clone($entity_init);
2009-02-05 03:42:58 +00:00
field_attach_insert($entity_type, $entity);
2009-05-01 15:28:13 +00:00
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
$this->assertTrue(empty($entity->{$this->field_name}), t('Insert: missing field results in no value saved'));
2009-02-05 03:42:58 +00:00
2009-04-29 21:33:00 +00:00
// Insert: Field is NULL.
2009-05-01 15:28:13 +00:00
field_cache_clear();
$entity = clone($entity_init);
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
$entity->{$this->field_name} = NULL;
2009-02-05 03:42:58 +00:00
field_attach_insert($entity_type, $entity);
2009-05-01 15:28:13 +00:00
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
$this->assertTrue(empty($entity->{$this->field_name}), t('Insert: NULL field results in no value saved'));
2009-05-01 15:28:13 +00:00
// Add some real data.
field_cache_clear();
$entity = clone($entity_init);
2009-05-17 03:12:17 +00:00
$values = $this->_generateTestFieldValues(1);
2009-08-22 00:58:55 +00:00
$entity->{$this->field_name}[$langcode] = $values;
2009-02-05 03:42:58 +00:00
field_attach_insert($entity_type, $entity);
2009-05-01 15:28:13 +00:00
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
2009-08-22 00:58:55 +00:00
$this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved'));
2009-02-05 03:42:58 +00:00
2009-02-10 03:16:15 +00:00
// Update: Field is missing. Data should survive.
2009-05-01 15:28:13 +00:00
field_cache_clear();
$entity = clone($entity_init);
2009-02-05 03:42:58 +00:00
field_attach_update($entity_type, $entity);
2009-05-01 15:28:13 +00:00
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
2009-08-22 00:58:55 +00:00
$this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Update: missing field leaves existing values in place'));
2009-02-05 03:42:58 +00:00
2009-02-10 03:16:15 +00:00
// Update: Field is NULL. Data should be wiped.
2009-05-01 15:28:13 +00:00
field_cache_clear();
$entity = clone($entity_init);
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
$entity->{$this->field_name} = NULL;
2009-02-05 03:42:58 +00:00
field_attach_update($entity_type, $entity);
2009-05-01 15:28:13 +00:00
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
$this->assertTrue(empty($entity->{$this->field_name}), t('Update: NULL field removes existing values'));
// Re-add some data.
field_cache_clear();
$entity = clone($entity_init);
$values = $this->_generateTestFieldValues(1);
$entity->{$this->field_name}[$langcode] = $values;
field_attach_update($entity_type, $entity);
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
$this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved'));
// Update: Field is empty array. Data should be wiped.
field_cache_clear();
$entity = clone($entity_init);
$entity->{$this->field_name} = array();
field_attach_update($entity_type, $entity);
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
$this->assertTrue(empty($entity->{$this->field_name}), t('Update: empty array removes existing values'));
2009-05-01 15:28:13 +00:00
}
/**
* Test insert with missing or NULL fields, with default value.
*/
function testFieldAttachSaveMissingDataDefaultValue() {
// Add a default value.
$this->instance['default_value_function'] = 'field_test_default_value';
field_update_instance($this->instance);
$entity_type = 'test_entity';
$entity_init = field_test_create_stub_entity();
2009-08-22 00:58:55 +00:00
$langcode = FIELD_LANGUAGE_NONE;
2009-05-01 15:28:13 +00:00
// Insert: Field is NULL.
$entity = clone($entity_init);
2009-08-22 00:58:55 +00:00
$entity->{$this->field_name}[$langcode] = NULL;
2009-05-01 15:28:13 +00:00
field_attach_insert($entity_type, $entity);
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
2009-08-22 00:58:55 +00:00
$this->assertTrue(empty($entity->{$this->field_name}[$langcode]), t('Insert: NULL field results in no value saved'));
2009-05-01 15:28:13 +00:00
// Insert: Field is missing.
field_cache_clear();
$entity = clone($entity_init);
field_attach_insert($entity_type, $entity);
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
$values = field_test_default_value($entity_type, $entity, $this->field, $this->instance);
2009-08-22 00:58:55 +00:00
$this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Insert: missing field results in default value saved'));
2009-02-05 03:42:58 +00:00
}
2009-08-26 02:59:22 +00:00
/**
* Test field_attach_delete().
*/
function testFieldAttachDelete() {
$entity_type = 'test_entity';
$langcode = FIELD_LANGUAGE_NONE;
$rev[0] = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
// Create revision 0
$values = $this->_generateTestFieldValues($this->field['cardinality']);
$rev[0]->{$this->field_name}[$langcode] = $values;
field_attach_insert($entity_type, $rev[0]);
// Create revision 1
$rev[1] = field_test_create_stub_entity(0, 1, $this->instance['bundle']);
$rev[1]->{$this->field_name}[$langcode] = $values;
field_attach_update($entity_type, $rev[1]);
// Create revision 2
$rev[2] = field_test_create_stub_entity(0, 2, $this->instance['bundle']);
$rev[2]->{$this->field_name}[$langcode] = $values;
field_attach_update($entity_type, $rev[2]);
// Confirm each revision loads
foreach (array_keys($rev) as $vid) {
$read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']);
field_attach_load_revision($entity_type, array(0 => $read));
$this->assertEqual(count($read->{$this->field_name}[$langcode]), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values.");
}
// Delete revision 1, confirm the other two still load.
field_attach_delete_revision($entity_type, $rev[1]);
foreach (array(0, 2) as $vid) {
$read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']);
field_attach_load_revision($entity_type, array(0 => $read));
$this->assertEqual(count($read->{$this->field_name}[$langcode]), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values.");
}
// Confirm the current revision still loads
$read = field_test_create_stub_entity(0, 2, $this->instance['bundle']);
field_attach_load($entity_type, array(0 => $read));
$this->assertEqual(count($read->{$this->field_name}[$langcode]), $this->field['cardinality'], "The test object current revision has {$this->field['cardinality']} values.");
// Delete all field data, confirm nothing loads
field_attach_delete($entity_type, $rev[2]);
foreach (array(0, 1, 2) as $vid) {
$read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']);
field_attach_load_revision($entity_type, array(0 => $read));
$this->assertIdentical($read->{$this->field_name}, array(), "The test object revision $vid is deleted.");
}
$read = field_test_create_stub_entity(0, 2, $this->instance['bundle']);
field_attach_load($entity_type, array(0 => $read));
$this->assertIdentical($read->{$this->field_name}, array(), t('The test object current revision is deleted.'));
}
/**
* Test field_attach_create_bundle() and field_attach_rename_bundle().
*/
function testFieldAttachCreateRenameBundle() {
// Create a new bundle. This has to be initiated by the module so that its
// hook_entity_info() is consistent.
$new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName());
field_test_create_bundle($new_bundle);
// Add an instance to that bundle.
$this->instance['bundle'] = $new_bundle;
field_create_instance($this->instance);
// Save an object with data in the field.
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
$langcode = FIELD_LANGUAGE_NONE;
$values = $this->_generateTestFieldValues($this->field['cardinality']);
$entity->{$this->field_name}[$langcode] = $values;
$entity_type = 'test_entity';
field_attach_insert($entity_type, $entity);
// Verify the field data is present on load.
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
field_attach_load($entity_type, array(0 => $entity));
$this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], "Data is retrieved for the new bundle");
// Rename the bundle. This has to be initiated by the module so that its
// hook_entity_info() is consistent.
$new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName());
field_test_rename_bundle($this->instance['bundle'], $new_bundle);
// Check that the instance definition has been updated.
$this->instance = field_info_instance($this->field_name, $new_bundle);
$this->assertIdentical($this->instance['bundle'], $new_bundle, "Bundle name has been updated in the instance.");
// Verify the field data is present on load.
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
$entity = field_test_create_stub_entity(0, 0, $new_bundle);
2009-08-26 02:59:22 +00:00
field_attach_load($entity_type, array(0 => $entity));
$this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], "Bundle name has been updated in the field storage");
}
/**
* Test field_attach_delete_bundle().
*/
function testFieldAttachDeleteBundle() {
// Create a new bundle. This has to be initiated by the module so that its
// hook_entity_info() is consistent.
$new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName());
field_test_create_bundle($new_bundle);
// Add an instance to that bundle.
$this->instance['bundle'] = $new_bundle;
field_create_instance($this->instance);
// Create a second field for the test bundle
$field_name = drupal_strtolower($this->randomName() . '_field_name');
$field = array('field_name' => $field_name, 'type' => 'test_field', 'cardinality' => 1);
field_create_field($field);
$instance = array(
'field_name' => $field_name,
'bundle' => $this->instance['bundle'],
'label' => $this->randomName() . '_label',
'description' => $this->randomName() . '_description',
'weight' => mt_rand(0, 127),
// test_field has no instance settings
'widget' => array(
'type' => 'test_field_widget',
'settings' => array(
'size' => mt_rand(0, 255))));
field_create_instance($instance);
// Save an object with data for both fields
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
$langcode = FIELD_LANGUAGE_NONE;
$values = $this->_generateTestFieldValues($this->field['cardinality']);
$entity->{$this->field_name}[$langcode] = $values;
$entity->{$field_name}[$langcode] = $this->_generateTestFieldValues(1);
$entity_type = 'test_entity';
field_attach_insert($entity_type, $entity);
// Verify the fields are present on load
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
field_attach_load($entity_type, array(0 => $entity));
$this->assertEqual(count($entity->{$this->field_name}[$langcode]), 4, 'First field got loaded');
$this->assertEqual(count($entity->{$field_name}[$langcode]), 1, 'Second field got loaded');
// Delete the bundle. This has to be initiated by the module so that its
// hook_entity_info() is consistent.
field_test_delete_bundle($this->instance['bundle']);
// Verify no data gets loaded
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
field_attach_load($entity_type, array(0 => $entity));
$this->assertFalse(isset($entity->{$this->field_name}[$langcode]), 'No data for first field');
$this->assertFalse(isset($entity->{$field_name}[$langcode]), 'No data for second field');
// Verify that the instances are gone
$this->assertFalse(field_read_instance($this->field_name, $this->instance['bundle']), "First field is deleted");
$this->assertFalse(field_read_instance($field_name, $instance['bundle']), "Second field is deleted");
}
2009-06-06 16:17:30 +00:00
/**
* Test field_attach_query().
*/
function testFieldAttachQuery() {
$cardinality = $this->field['cardinality'];
2009-08-22 00:58:55 +00:00
$langcode = FIELD_LANGUAGE_NONE;
2009-06-06 16:17:30 +00:00
// Create an additional bundle with an instance of the field.
field_test_create_bundle('test_bundle_1', 'Test Bundle 1');
$this->instance2 = $this->instance;
$this->instance2['bundle'] = 'test_bundle_1';
field_create_instance($this->instance2);
// Create two test objects, using two different types and bundles.
$entity_types = array(1 => 'test_entity', 2 => 'test_cacheable_entity');
$entities = array(1 => field_test_create_stub_entity(1, 1, 'test_bundle'), 2 => field_test_create_stub_entity(2, 2, 'test_bundle_1'));
// Create first test object with random (distinct) values.
$values = array();
for ($delta = 0; $delta < $cardinality; $delta++) {
do {
$value = mt_rand(1, 127);
} while (in_array($value, $values));
$values[$delta] = $value;
2009-08-22 00:58:55 +00:00
$entities[1]->{$this->field_name}[$langcode][$delta] = array('value' => $values[$delta]);
2009-06-06 16:17:30 +00:00
}
field_attach_insert($entity_types[1], $entities[1]);
// Create second test object, sharing a value with the first one.
$common_value = $values[$cardinality - 1];
2009-08-22 00:58:55 +00:00
$entities[2]->{$this->field_name} = array($langcode => array(array('value' => $common_value)));
2009-06-06 16:17:30 +00:00
field_attach_insert($entity_types[2], $entities[2]);
// Query on the object's values.
for ($delta = 0; $delta < $cardinality; $delta++) {
$conditions = array(array('value', $values[$delta]));
2009-08-11 14:59:40 +00:00
$result = field_attach_query($this->field_id, $conditions, FIELD_QUERY_NO_LIMIT);
2009-06-06 16:17:30 +00:00
$this->assertTrue(isset($result[$entity_types[1]][1]), t('Query on value %delta returns the object', array('%delta' => $delta)));
}
// Query on a value that is not in the object.
do {
$different_value = mt_rand(1, 127);
} while (in_array($different_value, $values));
$conditions = array(array('value', $different_value));
2009-08-11 14:59:40 +00:00
$result = field_attach_query($this->field_id, $conditions, FIELD_QUERY_NO_LIMIT);
2009-06-06 16:17:30 +00:00
$this->assertFalse(isset($result[$entity_types[1]][1]), t("Query on a value that is not in the object doesn't return the object"));
// Query on the value shared by both objects, and discriminate using
// additional conditions.
$conditions = array(array('value', $common_value));
2009-08-11 14:59:40 +00:00
$result = field_attach_query($this->field_id, $conditions, FIELD_QUERY_NO_LIMIT);
2009-06-06 16:17:30 +00:00
$this->assertTrue(isset($result[$entity_types[1]][1]) && isset($result[$entity_types[2]][2]), t('Query on a value common to both objects returns both objects'));
$conditions = array(array('type', $entity_types[1]), array('value', $common_value));
2009-08-11 14:59:40 +00:00
$result = field_attach_query($this->field_id, $conditions, FIELD_QUERY_NO_LIMIT);
2009-06-06 16:17:30 +00:00
$this->assertTrue(isset($result[$entity_types[1]][1]) && !isset($result[$entity_types[2]][2]), t("Query on a value common to both objects and a 'type' condition only returns the relevant object"));
$conditions = array(array('bundle', $entities[1]->fttype), array('value', $common_value));
2009-08-11 14:59:40 +00:00
$result = field_attach_query($this->field_id, $conditions, FIELD_QUERY_NO_LIMIT);
2009-06-06 16:17:30 +00:00
$this->assertTrue(isset($result[$entity_types[1]][1]) && !isset($result[$entity_types[2]][2]), t("Query on a value common to both objects and a 'bundle' condition only returns the relevant object"));
$conditions = array(array('entity_id', $entities[1]->ftid), array('value', $common_value));
2009-08-11 14:59:40 +00:00
$result = field_attach_query($this->field_id, $conditions, FIELD_QUERY_NO_LIMIT);
2009-06-06 16:17:30 +00:00
$this->assertTrue(isset($result[$entity_types[1]][1]) && !isset($result[$entity_types[2]][2]), t("Query on a value common to both objects and an 'entity_id' condition only returns the relevant object"));
2009-07-15 17:55:18 +00:00
// Test result format.
2009-06-06 16:17:30 +00:00
$conditions = array(array('value', $values[0]));
2009-08-11 14:59:40 +00:00
$result = field_attach_query($this->field_id, $conditions, FIELD_QUERY_NO_LIMIT);
2009-06-06 16:17:30 +00:00
$expected = array(
$entity_types[1] => array(
2009-07-07 09:28:07 +00:00
$entities[1]->ftid => field_test_create_stub_entity($entities[1]->ftid, $entities[1]->ftvid),
2009-06-06 16:17:30 +00:00
)
);
2009-07-15 17:55:18 +00:00
$this->assertEqual($result, $expected, t('Result format is correct.'));
// Now test the count/offset paging capability.
// Create a new bundle with an instance of the field.
field_test_create_bundle('offset_bundle', 'Offset Test Bundle');
$this->instance2 = $this->instance;
$this->instance2['bundle'] = 'offset_bundle';
field_create_instance($this->instance2);
// Create 20 test objects, using the new bundle, but with
// non-sequential ids so we can tell we are getting the right ones
// back. We do not need unique values since field_attach_query()
// won't return them anyway.
$offset_entities = array();
$offset_id = mt_rand(1, 3);
for ($i = 0; $i < 20; ++$i) {
$offset_id += mt_rand(2, 5);
$offset_entities[$offset_id] = field_test_create_stub_entity($offset_id, $offset_id, 'offset_bundle');
2009-08-22 00:58:55 +00:00
$offset_entities[$offset_id]->{$this->field_name}[$langcode][0] = array('value' => $offset_id);
2009-07-15 17:55:18 +00:00
field_attach_insert('test_entity', $offset_entities[$offset_id]);
}
// Query for the offset entities in batches, making sure we get
// back the right ones.
$cursor = 0;
foreach (array(1 => 1, 3 => 3, 5 => 5, 8 => 8, 13 => 3) as $count => $expect) {
2009-08-11 14:59:40 +00:00
$found = field_attach_query($this->field_id, array(array('bundle', 'offset_bundle')), $count, $cursor);
2009-07-15 17:55:18 +00:00
if (isset($found['test_entity'])) {
$this->assertEqual(count($found['test_entity']), $expect, t('Requested @count, expected @expect, got @found, cursor @cursor', array('@count' => $count, '@expect' => $expect, '@found' => count($found['test_entity']), '@cursor' => $cursor)));
foreach ($found['test_entity'] as $id => $entity) {
$this->assert(isset($offset_entities[$id]), "Entity $id found");
unset($offset_entities[$id]);
}
}
else {
$this->assertEqual(0, $expect, t('Requested @count, expected @expect, got @found, cursor @cursor', array('@count' => $count, '@expect' => $expect, '@found' => 0, '@cursor' => $cursor)));
}
}
$this->assertEqual(count($offset_entities), 0, "All entities found");
$this->assertEqual($cursor, FIELD_QUERY_COMPLETE, "Cursor is FIELD_QUERY_COMPLETE");
2009-06-06 16:17:30 +00:00
}
/**
* Test field_attach_query_revisions().
*/
function testFieldAttachQueryRevisions() {
$cardinality = $this->field['cardinality'];
// Create first object revision with random (distinct) values.
$entity_type = 'test_entity';
$entities = array(1 => field_test_create_stub_entity(1, 1), 2 => field_test_create_stub_entity(1, 2));
2009-08-22 00:58:55 +00:00
$langcode = FIELD_LANGUAGE_NONE;
2009-06-06 16:17:30 +00:00
$values = array();
for ($delta = 0; $delta < $cardinality; $delta++) {
do {
$value = mt_rand(1, 127);
} while (in_array($value, $values));
$values[$delta] = $value;
2009-08-22 00:58:55 +00:00
$entities[1]->{$this->field_name}[$langcode][$delta] = array('value' => $values[$delta]);
2009-06-06 16:17:30 +00:00
}
field_attach_insert($entity_type, $entities[1]);
// Create second object revision, sharing a value with the first one.
$common_value = $values[$cardinality - 1];
2009-08-22 00:58:55 +00:00
$entities[2]->{$this->field_name}[$langcode][0] = array('value' => $common_value);
2009-06-06 16:17:30 +00:00
field_attach_update($entity_type, $entities[2]);
// Query on the object's values.
for ($delta = 0; $delta < $cardinality; $delta++) {
$conditions = array(array('value', $values[$delta]));
2009-08-11 14:59:40 +00:00
$result = field_attach_query_revisions($this->field_id, $conditions, FIELD_QUERY_NO_LIMIT);
2009-06-06 16:17:30 +00:00
$this->assertTrue(isset($result[$entity_type][1]), t('Query on value %delta returns the object', array('%delta' => $delta)));
}
// Query on a value that is not in the object.
do {
$different_value = mt_rand(1, 127);
} while (in_array($different_value, $values));
$conditions = array(array('value', $different_value));
2009-08-11 14:59:40 +00:00
$result = field_attach_query_revisions($this->field_id, $conditions, FIELD_QUERY_NO_LIMIT);
2009-06-06 16:17:30 +00:00
$this->assertFalse(isset($result[$entity_type][1]), t("Query on a value that is not in the object doesn't return the object"));
// Query on the value shared by both objects, and discriminate using
// additional conditions.
$conditions = array(array('value', $common_value));
2009-08-11 14:59:40 +00:00
$result = field_attach_query_revisions($this->field_id, $conditions, FIELD_QUERY_NO_LIMIT);
2009-06-06 16:17:30 +00:00
$this->assertTrue(isset($result[$entity_type][1]) && isset($result[$entity_type][2]), t('Query on a value common to both objects returns both objects'));
$conditions = array(array('revision_id', $entities[1]->ftvid), array('value', $common_value));
2009-08-11 14:59:40 +00:00
$result = field_attach_query_revisions($this->field_id, $conditions, FIELD_QUERY_NO_LIMIT);
2009-06-06 16:17:30 +00:00
$this->assertTrue(isset($result[$entity_type][1]) && !isset($result[$entity_type][2]), t("Query on a value common to both objects and a 'revision_id' condition only returns the relevant object"));
// Test FIELD_QUERY_RETURN_IDS result format.
$conditions = array(array('value', $values[0]));
2009-08-11 14:59:40 +00:00
$result = field_attach_query_revisions($this->field_id, $conditions, FIELD_QUERY_NO_LIMIT);
2009-06-06 16:17:30 +00:00
$expected = array(
$entity_type => array(
2009-07-07 09:28:07 +00:00
$entities[1]->ftid => field_test_create_stub_entity($entities[1]->ftid, $entities[1]->ftvid),
2009-06-06 16:17:30 +00:00
)
);
$this->assertEqual($result, $expected, t('FIELD_QUERY_RETURN_IDS result format returns the expect result'));
}
2009-08-26 02:59:22 +00:00
}
2009-06-06 16:17:30 +00:00
2009-08-26 02:59:22 +00:00
/**
* Unit test class for non-storage related field_attach_* functions.
*/
class FieldAttachOtherTestCase extends FieldAttachTestCase {
public static function getInfo() {
return array(
'name' => 'Field attach tests (other)',
'description' => 'Test other Field Attach API functions.',
'group' => 'Field',
);
}
/**
* Test field_attach_views() and field_attach_preprocess().
*/
2009-02-05 03:42:58 +00:00
function testFieldAttachViewAndPreprocess() {
$entity_type = 'test_entity';
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
2009-08-22 00:58:55 +00:00
$langcode = FIELD_LANGUAGE_NONE;
2009-02-05 03:42:58 +00:00
// Populate values to be displayed.
2009-05-17 03:12:17 +00:00
$values = $this->_generateTestFieldValues($this->field['cardinality']);
2009-08-22 00:58:55 +00:00
$entity->{$this->field_name}[$langcode] = $values;
2009-02-05 03:42:58 +00:00
// Simple formatter, label displayed.
$formatter_setting = $this->randomName();
$this->instance['display'] = array(
'full' => array(
'label' => 'above',
'type' => 'field_test_default',
'settings' => array(
'test_formatter_setting' => $formatter_setting,
)
),
);
field_update_instance($this->instance);
$entity->content = field_attach_view($entity_type, $entity);
$output = drupal_render($entity->content);
$this->content = $output;
$this->assertRaw($this->instance['label'], "Label is displayed.");
foreach ($values as $delta => $value) {
$this->content = $output;
$this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
}
// Label hidden.
$this->instance['display']['full']['label'] = 'hidden';
field_update_instance($this->instance);
$entity->content = field_attach_view($entity_type, $entity);
$output = drupal_render($entity->content);
$this->content = $output;
$this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed.");
// Field hidden.
$this->instance['display'] = array(
'full' => array(
'label' => 'above',
'type' => 'hidden',
),
);
field_update_instance($this->instance);
$entity->content = field_attach_view($entity_type, $entity);
$output = drupal_render($entity->content);
$this->content = $output;
$this->assertNoRaw($this->instance['label'], "Hidden field: label is not displayed.");
foreach ($values as $delta => $value) {
$this->assertNoRaw($value['value'], "Hidden field: value $delta is not displayed.");
}
// Multiple formatter.
$formatter_setting = $this->randomName();
$this->instance['display'] = array(
'full' => array(
'label' => 'above',
'type' => 'field_test_multiple',
'settings' => array(
'test_formatter_setting_multiple' => $formatter_setting,
)
),
);
field_update_instance($this->instance);
$entity->content = field_attach_view($entity_type, $entity);
$output = drupal_render($entity->content);
$display = $formatter_setting;
foreach ($values as $delta => $value) {
$display .= "|$delta:{$value['value']}";
}
$this->content = $output;
$this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied.");
// TODO:
// - check display order with several fields
2009-08-22 00:58:55 +00:00
// Preprocess template.
$variables = array();
field_attach_preprocess($entity_type, $entity, $entity->content, $variables);
$result = TRUE;
foreach ($values as $delta => $item) {
if ($variables[$this->field_name][$delta]['value'] !== $item['value']) {
$result = FALSE;
break;
}
}
$this->assertTrue($result, t('Variable $@field_name correctly populated.', array('@field_name' => $this->field_name)));
2009-02-05 03:42:58 +00:00
}
2009-05-17 00:32:29 +00:00
/**
* Test field cache.
*/
2009-02-05 03:42:58 +00:00
function testFieldAttachCache() {
2009-05-17 00:32:29 +00:00
// Initialize random values and a test entity.
2009-06-16 08:40:18 +00:00
$entity_init = field_test_create_stub_entity(1, 1, $this->instance['bundle']);
2009-08-22 00:58:55 +00:00
$langcode = FIELD_LANGUAGE_NONE;
2009-05-17 03:12:17 +00:00
$values = $this->_generateTestFieldValues($this->field['cardinality']);
2009-02-05 03:42:58 +00:00
$noncached_type = 'test_entity';
$cached_type = 'test_cacheable_entity';
2009-05-17 00:32:29 +00:00
2009-05-17 03:12:17 +00:00
// Non-cacheable entity type.
2009-06-16 08:40:18 +00:00
$cid = "field:$noncached_type:{$entity_init->ftid}";
2009-02-05 03:42:58 +00:00
2009-05-17 03:12:17 +00:00
// Check that no initial cache entry is present.
$this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no initial cache entry'));
2009-02-05 03:42:58 +00:00
2009-05-17 03:12:17 +00:00
// Save, and check that no cache entry is present.
2009-06-16 08:40:18 +00:00
$entity = clone($entity_init);
2009-08-22 00:58:55 +00:00
$entity->{$this->field_name}[$langcode] = $values;
2009-02-05 03:42:58 +00:00
field_attach_insert($noncached_type, $entity);
2009-05-17 03:12:17 +00:00
$this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no cache entry on insert'));
2009-02-05 03:42:58 +00:00
2009-05-17 03:12:17 +00:00
// Load, and check that no cache entry is present.
2009-06-16 08:40:18 +00:00
$entity = clone($entity_init);
2009-05-17 03:12:17 +00:00
field_attach_load($noncached_type, array($entity->ftid => $entity));
$this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no cache entry on load'));
2009-02-05 03:42:58 +00:00
2009-05-17 00:32:29 +00:00
2009-05-17 03:12:17 +00:00
// Cacheable entity type.
2009-06-16 08:40:18 +00:00
$cid = "field:$cached_type:{$entity_init->ftid}";
2009-02-05 03:42:58 +00:00
2009-05-17 03:12:17 +00:00
// Check that no initial cache entry is present.
$this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no initial cache entry'));
2009-02-05 03:42:58 +00:00
2009-05-17 03:12:17 +00:00
// Save, and check that no cache entry is present.
2009-06-16 08:40:18 +00:00
$entity = clone($entity_init);
2009-08-22 00:58:55 +00:00
$entity->{$this->field_name}[$langcode] = $values;
2009-02-05 03:42:58 +00:00
field_attach_insert($cached_type, $entity);
2009-05-17 03:12:17 +00:00
$this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on insert'));
2009-02-05 03:42:58 +00:00
2009-08-11 14:59:40 +00:00
// Load a single field, and check that no cache entry is present.
$entity = clone($entity_init);
field_attach_load($cached_type, array($entity->ftid => $entity), FIELD_LOAD_CURRENT, array('field_id' => $this->field_id));
$cache = cache_get($cid, 'cache_field');
$this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on loading a single field'));
2009-05-17 03:12:17 +00:00
// Load, and check that a cache entry is present with the expected values.
2009-06-16 08:40:18 +00:00
$entity = clone($entity_init);
2009-05-17 03:12:17 +00:00
field_attach_load($cached_type, array($entity->ftid => $entity));
2009-02-05 03:42:58 +00:00
$cache = cache_get($cid, 'cache_field');
2009-08-22 00:58:55 +00:00
$this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load'));
2009-02-05 03:42:58 +00:00
2009-05-17 03:12:17 +00:00
// Update with different values, and check that the cache entry is wiped.
$values = $this->_generateTestFieldValues($this->field['cardinality']);
2009-06-16 08:40:18 +00:00
$entity = clone($entity_init);
2009-08-22 00:58:55 +00:00
$entity->{$this->field_name}[$langcode] = $values;
2009-05-17 03:12:17 +00:00
field_attach_update($cached_type, $entity);
$this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on update'));
// Load, and check that a cache entry is present with the expected values.
2009-06-16 08:40:18 +00:00
$entity = clone($entity_init);
2009-05-17 03:12:17 +00:00
field_attach_load($cached_type, array($entity->ftid => $entity));
$cache = cache_get($cid, 'cache_field');
2009-08-22 00:58:55 +00:00
$this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load'));
2009-05-17 03:12:17 +00:00
// Create a new revision, and check that the cache entry is wiped.
2009-06-16 08:40:18 +00:00
$entity_init = field_test_create_stub_entity(1, 2, $this->instance['bundle']);
2009-05-17 03:12:17 +00:00
$values = $this->_generateTestFieldValues($this->field['cardinality']);
2009-06-16 08:40:18 +00:00
$entity = clone($entity_init);
2009-08-22 00:58:55 +00:00
$entity->{$this->field_name}[$langcode] = $values;
2009-05-17 03:12:17 +00:00
field_attach_update($cached_type, $entity);
$cache = cache_get($cid, 'cache_field');
$this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on new revision creation'));
// Load, and check that a cache entry is present with the expected values.
2009-06-16 08:40:18 +00:00
$entity = clone($entity_init);
2009-05-17 03:12:17 +00:00
field_attach_load($cached_type, array($entity->ftid => $entity));
$cache = cache_get($cid, 'cache_field');
2009-08-22 00:58:55 +00:00
$this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load'));
2009-05-17 03:12:17 +00:00
// Delete, and check that the cache entry is wiped.
2009-02-05 03:42:58 +00:00
field_attach_delete($cached_type, $entity);
2009-05-17 03:12:17 +00:00
$this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry after delete'));
2009-02-05 03:42:58 +00:00
}
2009-08-26 02:59:22 +00:00
/**
* Test field_attach_validate().
*
* Verify that field_attach_validate() invokes the correct
* hook_field_validate.
*/
2009-02-05 03:42:58 +00:00
function testFieldAttachValidate() {
$entity_type = 'test_entity';
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
2009-08-22 00:58:55 +00:00
$langcode = FIELD_LANGUAGE_NONE;
2009-02-05 03:42:58 +00:00
// Set up values to generate errors
$values = array();
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
$values[$delta]['value'] = -1;
$values[$delta]['_error_element'] = 'field_error_' . $delta;
}
// Arrange for item 1 not to generate an error
$values[1]['value'] = 1;
2009-08-22 00:58:55 +00:00
$entity->{$this->field_name}[$langcode] = $values;
2009-02-05 03:42:58 +00:00
2009-03-26 13:31:28 +00:00
try {
field_attach_validate($entity_type, $entity);
}
catch (FieldValidationException $e) {
$errors = $e->errors;
}
2009-02-05 03:42:58 +00:00
foreach ($values as $delta => $value) {
if ($value['value'] != 1) {
2009-08-22 00:58:55 +00:00
$this->assertIdentical($errors[$this->field_name][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on value $delta");
$this->assertEqual(count($errors[$this->field_name][$langcode][$delta]), 1, "Only one error set on value $delta");
unset($errors[$this->field_name][$langcode][$delta]);
2009-02-05 03:42:58 +00:00
}
else {
2009-08-22 00:58:55 +00:00
$this->assertFalse(isset($errors[$this->field_name][$langcode][$delta]), "No error set on value $delta");
2009-02-05 03:42:58 +00:00
}
}
2009-08-22 00:58:55 +00:00
$this->assertEqual(count($errors[$this->field_name][$langcode]), 0, 'No extraneous errors set');
2009-02-05 03:42:58 +00:00
}
2009-08-26 02:59:22 +00:00
/**
* Test field_attach_form().
*
* This could be much more thorough, but it does verify that the correct
* widgets show up.
*/
2009-02-05 03:42:58 +00:00
function testFieldAttachForm() {
$entity_type = 'test_entity';
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
$form = $form_state = array();
field_attach_form($entity_type, $entity, $form, $form_state);
2009-08-22 00:58:55 +00:00
$langcode = FIELD_LANGUAGE_NONE;
$this->assertEqual($form[$this->field_name][$langcode]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}");
2009-02-05 03:42:58 +00:00
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
// field_test_widget uses 'textfield'
2009-08-22 00:58:55 +00:00
$this->assertEqual($form[$this->field_name][$langcode][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield");
2009-02-05 03:42:58 +00:00
}
}
2009-08-26 02:59:22 +00:00
/**
* Test field_attach_submit().
*/
2009-02-05 03:42:58 +00:00
function testFieldAttachSubmit() {
$entity_type = 'test_entity';
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
// Build the form.
$form = $form_state = array();
field_attach_form($entity_type, $entity, $form, $form_state);
// Simulate incoming values.
$values = array();
$weights = array();
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
$values[$delta]['value'] = mt_rand(1, 127);
// Assign random weight.
do {
$weight = mt_rand(0, $this->field['cardinality']);
} while (in_array($weight, $weights));
$weights[$delta] = $weight;
$values[$delta]['_weight'] = $weight;
}
// Leave an empty value. 'field_test' fields are empty if empty().
$values[1]['value'] = 0;
2009-08-22 00:58:55 +00:00
$langcode = FIELD_LANGUAGE_NONE;
$form_state['values'] = array($this->field_name => array($langcode => $values));
2009-02-05 03:42:58 +00:00
field_attach_submit($entity_type, $entity, $form, $form_state);
asort($weights);
$expected_values = array();
foreach ($weights as $key => $value) {
if ($key != 1) {
$expected_values[] = array('value' => $values[$key]['value']);
}
}
2009-08-22 00:58:55 +00:00
$this->assertIdentical($entity->{$this->field_name}[$langcode], $expected_values, 'Submit filters empty values');
2009-02-05 03:42:58 +00:00
}
}
2009-08-26 02:59:22 +00:00
class FieldInfoTestCase extends FieldTestCase {
2009-02-05 03:42:58 +00:00
2009-03-31 01:49:55 +00:00
public static function getInfo() {
2009-02-05 03:42:58 +00:00
return array(
2009-07-14 02:24:14 +00:00
'name' => 'Field info tests',
'description' => 'Get information about existing fields, instances and bundles.',
'group' => 'Field',
2009-02-05 03:42:58 +00:00
);
}
function setUp() {
2009-08-09 01:28:06 +00:00
parent::setUp('field_test');
2009-02-05 03:42:58 +00:00
}
2009-08-02 11:24:21 +00:00
/**
* Test that field types and field definitions are correcly cached.
*/
2009-02-05 03:42:58 +00:00
function testFieldInfo() {
// Test that field_test module's fields, widgets, and formatters show up.
$field_test_info = field_test_field_info();
$formatter_info = field_test_field_formatter_info();
$widget_info = field_test_field_widget_info();
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
$storage_info = field_test_field_storage_info();
2009-02-05 03:42:58 +00:00
$info = field_info_field_types();
foreach ($field_test_info as $t_key => $field_type) {
foreach ($field_type as $key => $val) {
$this->assertEqual($info[$t_key][$key], $val, t("Field type $t_key key $key is $val"));
}
$this->assertEqual($info[$t_key]['module'], 'field_test', t("Field type field_test module appears"));
}
$info = field_info_formatter_types();
foreach ($formatter_info as $f_key => $formatter) {
foreach ($formatter as $key => $val) {
$this->assertEqual($info[$f_key][$key], $val, t("Formatter type $f_key key $key is $val"));
}
$this->assertEqual($info[$f_key]['module'], 'field_test', t("Formatter type field_test module appears"));
}
$info = field_info_widget_types();
foreach ($widget_info as $w_key => $widget) {
foreach ($widget as $key => $val) {
$this->assertEqual($info[$w_key][$key], $val, t("Widget type $w_key key $key is $val"));
}
$this->assertEqual($info[$w_key]['module'], 'field_test', t("Widget type field_test module appears"));
}
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
$info = field_info_storage_types();
foreach ($storage_info as $s_key => $storage) {
foreach ($storage as $key => $val) {
$this->assertEqual($info[$s_key][$key], $val, t("Storage type $s_key key $key is $val"));
}
$this->assertEqual($info[$s_key]['module'], 'field_test', t("Storage type field_test module appears"));
}
2009-06-12 08:39:40 +00:00
// Verify that no unexpected instances exist.
$core_fields = field_info_fields();
2009-02-05 03:42:58 +00:00
$instances = field_info_instances(FIELD_TEST_BUNDLE);
$this->assertTrue(empty($instances), t('With no instances, info bundles is empty.'));
// Create a field, verify it shows up.
$field = array(
2009-02-09 21:52:15 +00:00
'field_name' => drupal_strtolower($this->randomName()),
2009-02-05 03:42:58 +00:00
'type' => 'test_field',
);
field_create_field($field);
$fields = field_info_fields();
2009-06-12 08:39:40 +00:00
$this->assertEqual(count($fields), count($core_fields) + 1, t('One new field exists'));
2009-02-05 03:42:58 +00:00
$this->assertEqual($fields[$field['field_name']]['field_name'], $field['field_name'], t('info fields contains field name'));
$this->assertEqual($fields[$field['field_name']]['type'], $field['type'], t('info fields contains field type'));
$this->assertEqual($fields[$field['field_name']]['module'], 'field_test', t('info fields contains field module'));
$settings = array('test_field_setting' => 'dummy test string');
foreach ($settings as $key => $val) {
$this->assertEqual($fields[$field['field_name']]['settings'][$key], $val, t("Field setting $key has correct default value $val"));
}
$this->assertEqual($fields[$field['field_name']]['cardinality'], 1, t('info fields contains cardinality 1'));
$this->assertEqual($fields[$field['field_name']]['active'], 1, t('info fields contains active 1'));
// Create an instance, verify that it shows up
$instance = array(
'field_name' => $field['field_name'],
'bundle' => FIELD_TEST_BUNDLE,
'label' => $this->randomName(),
'description' => $this->randomName(),
'weight' => mt_rand(0, 127),
// test_field has no instance settings
'widget' => array(
'type' => 'test_field_widget',
'settings' => array(
'test_setting' => 999)));
field_create_instance($instance);
$instances = field_info_instances($instance['bundle']);
$this->assertEqual(count($instances), 1, t('One instance shows up in info when attached to a bundle.'));
$this->assertTrue($instance < $instances[$instance['field_name']], t('Instance appears in info correctly'));
}
2009-08-02 11:24:21 +00:00
/**
* Test that cached field definitions are ready for current runtime context.
*/
function testFieldPrepare() {
$field_definition = array(
'field_name' => 'field',
'type' => 'test_field',
);
field_create_field($field_definition);
// Simulate a stored field definition missing a field setting (e.g. a
// third-party module adding a new field setting has been enabled, and
// existing fields do not know the setting yet).
2009-09-18 00:04:24 +00:00
$data = db_query('SELECT data FROM {field_config} WHERE field_name = :field_name', array(':field_name' => $field_definition['field_name']))->fetchField();
2009-08-02 11:24:21 +00:00
$data = unserialize($data);
$data['settings'] = array();
db_update('field_config')
->fields(array('data' => serialize($data)))
->condition('field_name', $field_definition['field_name'])
->execute();
field_cache_clear();
// Read the field back.
$field = field_info_field($field_definition['field_name']);
// Check that all expected settings are in place.
$field_type = field_info_field_types($field_definition['type']);
$this->assertIdentical($field['settings'], $field_type['settings'], t('All expected default field settings are present.'));
}
/**
* Test that cached instance definitions are ready for current runtime context.
*/
function testInstancePrepare() {
$field_definition = array(
'field_name' => 'field',
'type' => 'test_field',
);
field_create_field($field_definition);
$instance_definition = array(
'field_name' => $field_definition['field_name'],
'bundle' => FIELD_TEST_BUNDLE,
);
field_create_instance($instance_definition);
// Simulate a stored instance definition missing various settings (e.g. a
// third-party module adding instance, widget or display settings has been
// enabled, but existing instances do not know the new settings).
2009-09-18 00:04:24 +00:00
$data = db_query('SELECT data FROM {field_config_instance} WHERE field_name = :field_name AND bundle = :bundle', array(':field_name' => $instance_definition['field_name'], ':bundle' => $instance_definition['bundle']))->fetchField();
2009-08-02 11:24:21 +00:00
$data = unserialize($data);
$data['settings'] = array();
$data['widget']['settings'] = 'unavailable_widget';
$data['widget']['settings'] = array();
$data['display']['full']['type'] = 'unavailable_formatter';
$data['display']['full']['settings'] = array();
db_update('field_config_instance')
->fields(array('data' => serialize($data)))
->condition('field_name', $instance_definition['field_name'])
->condition('bundle', $instance_definition['bundle'])
->execute();
field_cache_clear();
// Read the instance back.
$instance = field_info_instance($instance_definition['field_name'], $instance_definition['bundle']);
// Check that all expected instance settings are in place.
$field_type = field_info_field_types($field_definition['type']);
$this->assertIdentical($instance['settings'], $field_type['instance_settings'] , t('All expected instance settings are present.'));
// Check that the default widget is used and expected settings are in place.
$this->assertIdentical($instance['widget']['type'], $field_type['default_widget'], t('Unavailable widget replaced with default widget.'));
$widget_type = field_info_widget_types($instance['widget']['type']);
$this->assertIdentical($instance['widget']['settings'], $widget_type['settings'] , t('All expected widget settings are present.'));
// Check that the default formatter is used and expected settings are in place.
foreach (field_build_modes('test_entity') as $build_mode => $label) {
$this->assertIdentical($instance['display'][$build_mode]['type'], $field_type['default_formatter'], t('Unavailable formatter replaced with default formatter in build_mode %build_mode', array('%build_mode' => $build_mode)));
$formatter_type = field_info_formatter_types($instance['display'][$build_mode]['type']);
$this->assertIdentical($instance['display'][$build_mode]['settings'], $formatter_type['settings'] , t('All expected formatter settings are present in build_mode %build_mode', array('%build_mode' => $build_mode)));
}
}
/**
* Test that the field_info settings convenience functions work.
*/
2009-02-05 03:42:58 +00:00
function testSettingsInfo() {
$info = field_test_field_info();
foreach ($info as $type => $data) {
$this->assertIdentical(field_info_field_settings($type), $data['settings'], "field_info_field_settings returns {$type}'s field settings");
$this->assertIdentical(field_info_instance_settings($type), $data['instance_settings'], "field_info_field_settings returns {$type}'s field instance settings");
}
$info = field_test_field_widget_info();
foreach ($info as $type => $data) {
$this->assertIdentical(field_info_widget_settings($type), $data['settings'], "field_info_widget_settings returns {$type}'s widget settings");
}
$info = field_test_field_formatter_info();
foreach ($info as $type => $data) {
$this->assertIdentical(field_info_formatter_settings($type), $data['settings'], "field_info_formatter_settings returns {$type}'s formatter settings");
}
}
}
2009-08-26 02:59:22 +00:00
class FieldFormTestCase extends FieldTestCase {
2009-03-31 01:49:55 +00:00
public static function getInfo() {
2009-02-05 03:42:58 +00:00
return array(
2009-07-14 02:24:14 +00:00
'name' => 'Field form tests',
'description' => 'Test Field form handling.',
'group' => 'Field',
2009-02-05 03:42:58 +00:00
);
}
function setUp() {
2009-08-09 01:28:06 +00:00
parent::setUp('field_test');
2009-02-05 03:42:58 +00:00
$web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content'));
$this->drupalLogin($web_user);
2009-04-29 12:08:28 +00:00
$this->field_single = array('field_name' => drupal_strtolower($this->randomName()), 'type' => 'test_field');
$this->field_multiple = array('field_name' => drupal_strtolower($this->randomName()), 'type' => 'test_field', 'cardinality' => 4);
$this->field_unlimited = array('field_name' => drupal_strtolower($this->randomName()), 'type' => 'test_field', 'cardinality' => FIELD_CARDINALITY_UNLIMITED);
2009-02-05 03:42:58 +00:00
$this->instance = array(
'bundle' => 'test_bundle',
2009-04-29 21:33:00 +00:00
'label' => $this->randomName() . '_label',
'description' => $this->randomName() . '_description',
2009-02-05 03:42:58 +00:00
'weight' => mt_rand(0, 127),
'settings' => array(
'test_instance_setting' => $this->randomName(),
),
'widget' => array(
'type' => 'test_field_widget',
'label' => 'Test Field',
'settings' => array(
'test_widget_setting' => $this->randomName(),
)
)
);
}
function testFieldFormSingle() {
$this->field = $this->field_single;
$this->field_name = $this->field['field_name'];
$this->instance['field_name'] = $this->field_name;
field_create_field($this->field);
field_create_instance($this->instance);
2009-08-22 00:58:55 +00:00
$langcode = FIELD_LANGUAGE_NONE;
2009-02-05 03:42:58 +00:00
// Display creation form.
$this->drupalGet('test-entity/add/test-bundle');
2009-08-22 00:58:55 +00:00
$this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget is displayed');
$this->assertNoField("{$this->field_name}[$langcode][1][value]", 'No extraneous widget is displayed');
2009-02-05 03:42:58 +00:00
// TODO : check that the widget is populated with default value ?
// Submit with invalid value (field-level validation).
2009-08-22 00:58:55 +00:00
$edit = array("{$this->field_name}[$langcode][0][value]" => -1);
2009-02-05 03:42:58 +00:00
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertRaw(t('%name does not accept the value -1.', array('%name' => $this->instance['label'])), 'Field validation fails with invalid input.');
// TODO : check that the correct field is flagged for error.
// Create an entity
2009-02-10 02:04:01 +00:00
$value = mt_rand(1, 127);
2009-08-22 00:58:55 +00:00
$edit = array("{$this->field_name}[$langcode][0][value]" => $value);
2009-02-05 03:42:58 +00:00
$this->drupalPost(NULL, $edit, t('Save'));
preg_match('|test-entity/(\d+)/edit|', $this->url, $match);
$id = $match[1];
$this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created');
$entity = field_test_entity_load($id);
2009-08-22 00:58:55 +00:00
$this->assertEqual($entity->{$this->field_name}[$langcode][0]['value'], $value, 'Field value was saved');
2009-02-05 03:42:58 +00:00
// Display edit form.
$this->drupalGet('test-entity/' . $id . '/edit');
2009-08-22 00:58:55 +00:00
$this->assertFieldByName("{$this->field_name}[$langcode][0][value]", $value, 'Widget is displayed with the correct default value');
$this->assertNoField("{$this->field_name}[$langcode][1][value]", 'No extraneous widget is displayed');
2009-02-05 03:42:58 +00:00
// Update the entity.
2009-02-10 02:04:01 +00:00
$value = mt_rand(1, 127);
2009-08-22 00:58:55 +00:00
$edit = array("{$this->field_name}[$langcode][0][value]" => $value);
2009-02-05 03:42:58 +00:00
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated');
$entity = field_test_entity_load($id);
2009-08-22 00:58:55 +00:00
$this->assertEqual($entity->{$this->field_name}[$langcode][0]['value'], $value, 'Field value was updated');
2009-02-05 03:42:58 +00:00
// Empty the field.
$value = '';
2009-08-22 00:58:55 +00:00
$edit = array("{$this->field_name}[$langcode][0][value]" => $value);
2009-02-05 03:42:58 +00:00
$this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save'));
$this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated');
$entity = field_test_entity_load($id);
$this->assertIdentical($entity->{$this->field_name}, array(), 'Field was emptied');
}
function testFieldFormSingleRequired() {
$this->field = $this->field_single;
$this->field_name = $this->field['field_name'];
$this->instance['field_name'] = $this->field_name;
$this->instance['required'] = TRUE;
field_create_field($this->field);
field_create_instance($this->instance);
2009-08-22 00:58:55 +00:00
$langcode = FIELD_LANGUAGE_NONE;
2009-02-05 03:42:58 +00:00
// Submit with missing required value.
$edit = array();
$this->drupalPost('test-entity/add/test-bundle', $edit, t('Save'));
$this->assertRaw(t('!name field is required.', array('!name' => $this->instance['label'])), 'Required field with no value fails validation');
// Create an entity
2009-02-10 02:04:01 +00:00
$value = mt_rand(1, 127);
2009-08-22 00:58:55 +00:00
$edit = array("{$this->field_name}[$langcode][0][value]" => $value);
2009-02-05 03:42:58 +00:00
$this->drupalPost(NULL, $edit, t('Save'));
preg_match('|test-entity/(\d+)/edit|', $this->url, $match);
$id = $match[1];
$this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created');
$entity = field_test_entity_load($id);
2009-08-22 00:58:55 +00:00
$this->assertEqual($entity->{$this->field_name}[$langcode][0]['value'], $value, 'Field value was saved');
2009-02-05 03:42:58 +00:00
// Edit with missing required value.
$value = '';
2009-08-22 00:58:55 +00:00
$edit = array("{$this->field_name}[$langcode][0][value]" => $value);
2009-02-05 03:42:58 +00:00
$this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save'));
$this->assertRaw(t('!name field is required.', array('!name' => $this->instance['label'])), 'Required field with no value fails validation');
}
// function testFieldFormMultiple() {
// $this->field = $this->field_multiple;
// $this->field_name = $this->field['field_name'];
// $this->instance['field_name'] = $this->field_name;
// field_create_field($this->field);
// field_create_instance($this->instance);
// }
function testFieldFormUnlimited() {
$this->field = $this->field_unlimited;
$this->field_name = $this->field['field_name'];
$this->instance['field_name'] = $this->field_name;
field_create_field($this->field);
field_create_instance($this->instance);
2009-08-22 00:58:55 +00:00
$langcode = FIELD_LANGUAGE_NONE;
2009-02-05 03:42:58 +00:00
// Display creation form -> 1 widget.
$this->drupalGet('test-entity/add/test-bundle');
2009-08-22 00:58:55 +00:00
$this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget 1 is displayed');
$this->assertNoField("{$this->field_name}[$langcode][1][value]", 'No extraneous widget is displayed');
2009-02-05 03:42:58 +00:00
// Press 'add more' button -> 2 widgets.
$this->drupalPost(NULL, array(), t('Add another item'));
2009-08-22 00:58:55 +00:00
$this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget 1 is displayed');
$this->assertFieldByName("{$this->field_name}[$langcode][1][value]", '', 'New widget is displayed');
$this->assertNoField("{$this->field_name}[$langcode][2][value]", 'No extraneous widget is displayed');
2009-02-05 03:42:58 +00:00
// TODO : check that non-field inpurs are preserved ('title')...
// Yet another time so that we can play with more values -> 3 widgets.
$this->drupalPost(NULL, array(), t('Add another item'));
// Prepare values and weights.
$count = 3;
$delta_range = $count - 1;
$values = $weights = $pattern = $expected_values = $edit = array();
for ($delta = 0; $delta <= $delta_range; $delta++) {
// Assign unique random weights.
do {
$weight = mt_rand(-$delta_range, $delta_range);
} while (in_array($weight, $weights));
$weights[] = $weight;
2009-02-10 02:04:01 +00:00
$value = mt_rand(1, 127);
2009-08-22 00:58:55 +00:00
$edit["$this->field_name[$langcode][$delta][value]"] = $value;
$edit["$this->field_name[$langcode][$delta][_weight]"] = $weight;
2009-02-05 03:42:58 +00:00
// We'll need three slightly different formats to check the values.
$values[$weight] = $value;
$field_values[$weight]['value'] = (string)$value;
2009-05-03 09:49:32 +00:00
$pattern[$weight] = "<input [^>]*value=\"$value\" [^>]*";
2009-02-05 03:42:58 +00:00
}
// Press 'add more' button -> 4 widgets
$this->drupalPost(NULL, $edit, t('Add another item'));
ksort($values);
$values = array_values($values);
for ($delta = 0; $delta <= $delta_range; $delta++) {
2009-08-22 00:58:55 +00:00
$this->assertFieldByName("$this->field_name[$langcode][$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value");
$this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $delta, "Widget $delta has the right weight");
2009-02-05 03:42:58 +00:00
}
ksort($pattern);
$pattern = implode('.*', array_values($pattern));
$this->assertPattern("|$pattern|s", 'Widgets are displayed in the correct order');
2009-08-22 00:58:55 +00:00
$this->assertFieldByName("$this->field_name[$langcode][$delta][value]", '', "New widget is displayed");
$this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $delta, "New widget has the right weight");
$this->assertNoField("$this->field_name[$langcode][" . ($delta + 1) . '][value]', 'No extraneous widget is displayed');
2009-02-05 03:42:58 +00:00
// Submit the form and create the entity.
$this->drupalPost(NULL, $edit, t('Save'));
preg_match('|test-entity/(\d+)/edit|', $this->url, $match);
$id = $match[1];
$this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created');
$entity = field_test_entity_load($id);
ksort($field_values);
$field_values = array_values($field_values);
2009-08-22 00:58:55 +00:00
$this->assertIdentical($entity->{$this->field_name}[$langcode], $field_values, 'Field values were saved in the correct order');
2009-02-05 03:42:58 +00:00
2009-05-26 09:12:29 +00:00
// Display edit form: check that the expected number of widgets is
// displayed, with correct values change values, reorder, leave an empty
// value in the middle.
// Submit: check that the entity is updated with correct values
// Re-submit: check that the field can be emptied.
2009-02-05 03:42:58 +00:00
// Test with several multiple fields in a form
}
2009-05-26 09:12:29 +00:00
// Check with a multiple widget (implement a textfield with comma separated values).
2009-02-05 03:42:58 +00:00
2009-05-26 09:12:29 +00:00
// Check inaccessible fields are preserved on update.
// Check inaccessible fields get default value on insert (not implemented yet).
2009-02-05 03:42:58 +00:00
2009-05-03 09:49:32 +00:00
function testFieldFormJSAddMore() {
$this->field = $this->field_unlimited;
$this->field_name = $this->field['field_name'];
$this->instance['field_name'] = $this->field_name;
field_create_field($this->field);
field_create_instance($this->instance);
2009-08-22 00:58:55 +00:00
$langcode = FIELD_LANGUAGE_NONE;
2009-05-03 09:49:32 +00:00
// Display creation form -> 1 widget.
$this->drupalGet('test-entity/add/test-bundle');
2009-05-26 09:12:29 +00:00
// Press 'add more' button a couple times -> 3 widgets.
// The drupalPostAhah() helper will not work iteratively, so we add those.
2009-05-03 09:49:32 +00:00
// through non-'JS' submission.
$this->drupalPost(NULL, array(), t('Add another item'));
$this->drupalPost(NULL, array(), t('Add another item'));
// Prepare values and weights.
$count = 3;
$delta_range = $count - 1;
$values = $weights = $pattern = $expected_values = $edit = array();
for ($delta = 0; $delta <= $delta_range; $delta++) {
// Assign unique random weights.
do {
$weight = mt_rand(-$delta_range, $delta_range);
} while (in_array($weight, $weights));
$weights[] = $weight;
$value = mt_rand(1, 127);
2009-08-22 00:58:55 +00:00
$edit["$this->field_name[$langcode][$delta][value]"] = $value;
$edit["$this->field_name[$langcode][$delta][_weight]"] = $weight;
2009-05-03 09:49:32 +00:00
// We'll need three slightly different formats to check the values.
$values[$weight] = $value;
$field_values[$weight]['value'] = (string)$value;
$pattern[$weight] = "<input [^>]*value=\"$value\" [^>]*";
}
2009-05-17 03:12:17 +00:00
// Press 'add more' button through AHAH.
2009-08-27 04:40:12 +00:00
$this->_fieldPostAhah($edit, t('Add another item'));
2009-05-03 09:49:32 +00:00
ksort($values);
$values = array_values($values);
for ($delta = 0; $delta <= $delta_range; $delta++) {
2009-08-22 00:58:55 +00:00
$this->assertFieldByName("$this->field_name[$langcode][$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value");
$this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $delta, "Widget $delta has the right weight");
2009-05-03 09:49:32 +00:00
}
ksort($pattern);
$pattern = implode('.*', array_values($pattern));
$this->assertPattern("|$pattern|s", 'Widgets are displayed in the correct order');
2009-08-22 00:58:55 +00:00
$this->assertFieldByName("$this->field_name[$langcode][$delta][value]", '', "New widget is displayed");
$this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $delta, "New widget has the right weight");
$this->assertNoField("$this->field_name[$langcode][" . ($delta + 1) . '][value]', 'No extraneous widget is displayed');
2009-05-03 09:49:32 +00:00
}
/**
* Execute a POST request on a AHAH callback.
*
* Stolen from poll.test. The JSON result is parsed into HTML and placed in
* $this->content, so that regular asserts can be performed.
*
* Since the result is generally not a full-fledged form, this cannot be
* called iteratively.
*/
2009-08-27 04:40:12 +00:00
function _fieldPostAhah($edit, $submit, array $options = array(), array $headers = array()) {
$this->additionalCurlOptions[CURLOPT_URL] = url('system/ajax', array('absolute' => TRUE));
2009-05-03 09:49:32 +00:00
$this->drupalPost(NULL, $edit, $submit);
unset($this->additionalCurlOptions[CURLOPT_URL]);
2009-09-21 07:56:09 +00:00
// The response is drupal_json_output, so we need to undo some escaping.
2009-08-17 07:12:16 +00:00
$commands = json_decode(str_replace(array('\x3c', '\x3e', '\x26'), array("<", ">", "&"), $this->drupalGetContent()));
// The JSON response will be two AJAX commands. The first is a settings
// command and the second is the replace command.
$settings = reset($commands);
$replace = next($commands);
$this->assertTrue(is_object($settings), t('The response settings command is an object'));
$this->assertTrue(is_object($replace), t('The response replace command is an object'));
2009-05-03 09:49:32 +00:00
// This response data is valid HTML so we will can reuse everything we have
// for HTML pages.
2009-08-17 07:12:16 +00:00
$this->content = $replace->data;
2009-05-03 09:49:32 +00:00
// Needs to be emptied out so the new content will be parsed.
$this->elements = '';
}
2009-02-05 03:42:58 +00:00
}
2009-08-26 02:59:22 +00:00
class FieldCrudTestCase extends FieldTestCase {
2009-03-31 01:49:55 +00:00
public static function getInfo() {
2009-02-05 03:42:58 +00:00
return array(
2009-07-14 02:24:14 +00:00
'name' => 'Field CRUD tests',
2009-09-26 15:57:39 +00:00
'description' => 'Test field create, read, update, and delete.',
2009-07-14 02:24:14 +00:00
'group' => 'Field',
2009-02-05 03:42:58 +00:00
);
}
function setUp() {
2009-09-26 15:57:39 +00:00
// field_update_field() tests use number.module
parent::setUp('field_test', 'number');
2009-02-05 03:42:58 +00:00
}
// TODO : test creation with
// - a full fledged $field structure, check that all the values are there
// - a minimal $field structure, check all default values are set
// defer actual $field comparison to a helper function, used for the two cases above
2009-07-14 10:27:29 +00:00
2009-02-05 03:42:58 +00:00
/**
* Test the creation of a field.
*/
function testCreateField() {
$field_definition = array(
2009-05-20 09:48:47 +00:00
'field_name' => 'field_2',
2009-02-05 03:42:58 +00:00
'type' => 'test_field',
);
2009-07-02 20:19:48 +00:00
field_test_memorize();
2009-05-28 10:05:32 +00:00
$field_definition = field_create_field($field_definition);
2009-07-02 20:19:48 +00:00
$mem = field_test_memorize();
$this->assertIdentical($mem['field_test_field_create_field'][0][0], $field_definition, 'hook_field_create_field() called with correct arguments.');
2009-07-14 10:27:29 +00:00
// Read the raw record from the {field_config_instance} table.
$result = db_query('SELECT * FROM {field_config} WHERE field_name = :field_name', array(':field_name' => $field_definition['field_name']));
$record = $result->fetchAssoc();
$record['data'] = unserialize($record['data']);
2009-02-05 03:42:58 +00:00
// Ensure that basic properties are preserved.
2009-07-14 10:27:29 +00:00
$this->assertEqual($record['field_name'], $field_definition['field_name'], t('The field name is properly saved.'));
$this->assertEqual($record['type'], $field_definition['type'], t('The field type is properly saved.'));
2009-02-05 03:42:58 +00:00
// Ensure that cardinality defaults to 1.
2009-07-14 10:27:29 +00:00
$this->assertEqual($record['cardinality'], 1, t('Cardinality defaults to 1.'));
2009-02-05 03:42:58 +00:00
// Ensure that default settings are present.
2009-07-14 10:27:29 +00:00
$field_type = field_info_field_types($field_definition['type']);
$this->assertIdentical($record['data']['settings'], $field_type['settings'], t('Default field settings have been written.'));
2009-02-05 03:42:58 +00:00
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
// Ensure that default storage was set.
$this->assertEqual($record['storage_type'], variable_get('field_storage_default'), t('The field type is properly saved.'));
2009-02-05 03:42:58 +00:00
// Guarantee that the name is unique.
try {
field_create_field($field_definition);
$this->fail(t('Cannot create two fields with the same name.'));
2009-04-29 21:33:00 +00:00
}
catch (FieldException $e) {
2009-02-05 03:42:58 +00:00
$this->pass(t('Cannot create two fields with the same name.'));
}
2009-08-10 21:19:42 +00:00
// Check that field type is required.
2009-02-05 03:42:58 +00:00
try {
2009-08-10 21:19:42 +00:00
$field_definition = array(
'field_name' => 'field_1',
);
field_create_field($field_definition);
$this->fail(t('Cannot create a field with no type.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot create a field with no type.'));
}
// Check that field name is required.
try {
$field_definition = array(
'type' => 'test_field'
);
field_create_field($field_definition);
$this->fail(t('Cannot create an unnamed field.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot create an unnamed field.'));
}
// Check that field name must start with a letter or _.
try {
$field_definition = array(
'field_name' => '2field_2',
'type' => 'test_field',
);
field_create_field($field_definition);
$this->fail(t('Cannot create a field with a name starting with a digit.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot create a field with a name starting with a digit.'));
}
// Check that field name must only contain lowercase alphanumeric or _.
try {
$field_definition = array(
'field_name' => 'field#_3',
'type' => 'test_field',
);
field_create_field($field_definition);
$this->fail(t('Cannot create a field with a name containing an illegal character.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot create a field with a name containing an illegal character.'));
}
// Check that field name cannot be longer than 32 characters long.
try {
$field_definition = array(
'field_name' => '_12345678901234567890123456789012',
'type' => 'test_field',
);
2009-02-05 03:42:58 +00:00
field_create_field($field_definition);
2009-08-10 21:19:42 +00:00
$this->fail(t('Cannot create a field with a name longer than 32 characters.'));
2009-04-29 21:33:00 +00:00
}
catch (FieldException $e) {
2009-08-10 21:19:42 +00:00
$this->pass(t('Cannot create a field with a name longer than 32 characters.'));
2009-02-05 03:42:58 +00:00
}
2009-09-11 03:42:34 +00:00
// Check that field name can not be an object key.
// "ftvid" is known as an object key from the "test_entity" type.
try {
$field_definition = array(
'type' => 'test_field',
'field_name' => 'ftvid',
);
$field = field_create_field($field_definition);
$this->fail(t('Cannot create a field bearing the name of an object key.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot create a field bearing the name of an object key.'));
}
2009-02-05 03:42:58 +00:00
}
2009-09-22 08:44:04 +00:00
/**
* Test failure to create a field.
*/
function testCreateFieldFail() {
$field_name = 'duplicate';
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
$field_definition = array('field_name' => $field_name, 'type' => 'test_field', 'storage' => array('type' => 'field_test_storage_failure'));
2009-09-22 08:44:04 +00:00
$query = db_select('field_config')->condition('field_name', $field_name)->countQuery();
// The field does not appear in field_config.
$count = $query->execute()->fetchField();
$this->assertEqual($count, 0, 'A field_config row for the field does not exist.');
// Try to create the field.
try {
$field = field_create_field($field_definition);
$this->assertTrue(FALSE, 'Field creation (correctly) fails.');
}
catch (Exception $e) {
$this->assertTrue(TRUE, 'Field creation (correctly) fails.');
}
// The field does not appear in field_config.
$count = $query->execute()->fetchField();
$this->assertEqual($count, 0, 'A field_config row for the field does not exist.');
}
2009-07-14 10:27:29 +00:00
/**
* Test reading back a field definition.
*/
function testReadField() {
$field_definition = array(
'field_name' => 'field_1',
'type' => 'test_field',
);
field_create_field($field_definition);
// Read the field back.
$field = field_read_field($field_definition['field_name']);
2009-08-02 11:24:21 +00:00
$this->assertTrue($field_definition < $field, t('The field was properly read.'));
2009-07-14 10:27:29 +00:00
}
2009-05-20 09:48:47 +00:00
/**
* Test creation of indexes on data column.
*/
function testFieldIndexes() {
// Check that indexes specified by the field type are used by default.
$field_definition = array(
'field_name' => 'field_1',
'type' => 'test_field',
);
field_create_field($field_definition);
$field = field_read_field($field_definition['field_name']);
$expected_indexes = array('value' => array('value'));
$this->assertEqual($field['indexes'], $expected_indexes, t('Field type indexes saved by default'));
// Check that indexes specified by the field definition override the field
// type indexes.
$field_definition = array(
'field_name' => 'field_2',
'type' => 'test_field',
'indexes' => array(
'value' => array(),
),
);
field_create_field($field_definition);
$field = field_read_field($field_definition['field_name']);
$expected_indexes = array('value' => array());
$this->assertEqual($field['indexes'], $expected_indexes, t('Field definition indexes override field type indexes'));
// Check that indexes specified by the field definition add to the field
// type indexes.
$field_definition = array(
'field_name' => 'field_3',
'type' => 'test_field',
'indexes' => array(
'value_2' => array('value'),
),
);
field_create_field($field_definition);
$field = field_read_field($field_definition['field_name']);
$expected_indexes = array('value' => array('value'), 'value_2' => array('value'));
$this->assertEqual($field['indexes'], $expected_indexes, t('Field definition indexes are merged with field type indexes'));
}
2009-02-05 03:42:58 +00:00
/**
* Test the deletion of a field.
*/
function testDeleteField() {
// TODO: Also test deletion of the data stored in the field ?
// Create two fields (so we can test that only one is deleted).
2009-05-20 09:48:47 +00:00
$this->field = array('field_name' => 'field_1', 'type' => 'test_field');
2009-04-20 02:46:25 +00:00
field_create_field($this->field);
2009-05-20 09:48:47 +00:00
$this->another_field = array('field_name' => 'field_2', 'type' => 'test_field');
2009-04-20 02:46:25 +00:00
field_create_field($this->another_field);
2009-02-05 03:42:58 +00:00
// Create instances for each.
$this->instance_definition = array(
'field_name' => $this->field['field_name'],
'bundle' => FIELD_TEST_BUNDLE,
'widget' => array(
'type' => 'test_field_widget',
),
);
field_create_instance($this->instance_definition);
$this->another_instance_definition = $this->instance_definition;
$this->another_instance_definition['field_name'] = $this->another_field['field_name'];
field_create_instance($this->another_instance_definition);
// Test that the first field is not deleted, and then delete it.
$field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE));
$this->assertTrue(!empty($field) && empty($field['deleted']), t('A new field is not marked for deletion.'));
field_delete_field($this->field['field_name']);
// Make sure that the field is marked as deleted when it is specifically
// loaded.
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
$field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE));
2009-02-05 03:42:58 +00:00
$this->assertTrue(!empty($field['deleted']), t('A deleted field is marked for deletion.'));
// Make sure that this field's instance is marked as deleted when it is
// specifically loaded.
$instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE));
$this->assertTrue(!empty($instance['deleted']), t('An instance for a deleted field is marked for deletion.'));
// Try to load the field normally and make sure it does not show up.
$field = field_read_field($this->field['field_name']);
$this->assertTrue(empty($field), t('A deleted field is not loaded by default.'));
// Try to load the instance normally and make sure it does not show up.
$instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
$this->assertTrue(empty($instance), t('An instance for a deleted field is not loaded by default.'));
// Make sure the other field (and its field instance) are not deleted.
$another_field = field_read_field($this->another_field['field_name']);
$this->assertTrue(!empty($another_field) && empty($another_field['deleted']), t('A non-deleted field is not marked for deletion.'));
$another_instance = field_read_instance($this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']);
$this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('An instance of a non-deleted field is not marked for deletion.'));
2009-05-28 10:05:32 +00:00
// Try to create a new field the same name as a deleted field and
// write data into it.
field_create_field($this->field);
field_create_instance($this->instance_definition);
$field = field_read_field($this->field['field_name']);
$this->assertTrue(!empty($field) && empty($field['deleted']), t('A new field with a previously used name is created.'));
$instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
$this->assertTrue(!empty($instance) && empty($instance['deleted']), t('A new instance for a previously used field name is created.'));
// Save an object with data for the field
$entity = field_test_create_stub_entity(0, 0, $instance['bundle']);
2009-08-22 00:58:55 +00:00
$langcode = FIELD_LANGUAGE_NONE;
2009-05-28 10:05:32 +00:00
$values[0]['value'] = mt_rand(1, 127);
2009-08-22 00:58:55 +00:00
$entity->{$field['field_name']}[$langcode] = $values;
2009-05-28 10:05:32 +00:00
$entity_type = 'test_entity';
field_attach_insert($entity_type, $entity);
// Verify the field is present on load
$entity = field_test_create_stub_entity(0, 0, $this->instance_definition['bundle']);
field_attach_load($entity_type, array(0 => $entity));
2009-08-22 00:58:55 +00:00
$this->assertIdentical(count($entity->{$field['field_name']}[$langcode]), count($values), "Data in previously deleted field saves and loads correctly");
2009-05-28 10:05:32 +00:00
foreach ($values as $delta => $value) {
2009-08-22 00:58:55 +00:00
$this->assertEqual($entity->{$field['field_name']}[$langcode][$delta]['value'], $values[$delta]['value'], "Data in previously deleted field saves and loads correctly");
2009-05-28 10:05:32 +00:00
}
2009-02-05 03:42:58 +00:00
}
2009-09-26 15:57:39 +00:00
function testUpdateNonExistentField() {
$test_field = array('field_name' => 'does_not_exist', 'type' => 'number_decimal');
try {
field_update_field($test_field);
$this->fail(t('Cannot update a field that does not exist.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot update a field that does not exist.'));
}
}
function testUpdateFieldType() {
$field = array('field_name' => 'field_type', 'type' => 'number_decimal');
$field = field_create_field($field);
$test_field = array('field_name' => 'field_type', 'type' => 'number_integer');
try {
field_update_field($test_field);
$this->fail(t('Cannot update a field to a different type.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot update a field to a different type.'));
}
}
/**
* Test updating a field.
*/
function testUpdateField() {
// Create a decimal 5.2 field.
$field = array('field_name' => 'decimal53', 'type' => 'number_decimal', 'cardinality' => 3, 'settings' => array('precision' => 5, 'scale' => 2));
$field = field_create_field($field);
$instance = array('field_name' => 'decimal53', 'bundle' => FIELD_TEST_BUNDLE);
$instance = field_create_instance($instance);
// Update it to a deciaml 5.3 field.
$field['settings']['scale'] = 3;
field_update_field($field);
// Save values with 2, 3, and 4 decimal places.
$entity = field_test_create_stub_entity(0, 0, $instance['bundle']);
$entity->decimal53[FIELD_LANGUAGE_NONE][0]['value'] = '1.23';
$entity->decimal53[FIELD_LANGUAGE_NONE][1]['value'] = '1.235';
$entity->decimal53[FIELD_LANGUAGE_NONE][2]['value'] = '1.2355';
field_attach_insert('test_entity', $entity);
$entity = field_test_create_stub_entity(0, 0, $instance['bundle']);
// Verify that the updated 5.3 field rounds to 3 decimal places.
field_attach_load('test_entity', array(0 => $entity));
$this->assertEqual($entity->decimal53[FIELD_LANGUAGE_NONE][0]['value'], '1.23', t('2 decimal places are left alone'));
$this->assertEqual($entity->decimal53[FIELD_LANGUAGE_NONE][1]['value'], '1.235', t('3 decimal places are left alone'));
$this->assertEqual($entity->decimal53[FIELD_LANGUAGE_NONE][2]['value'], '1.236', t('4 decimal places are rounded to 3'));
}
/**
* Test field type modules forbidding an update.
*/
function testUpdateFieldForbid() {
$field = array('field_name' => 'forbidden', 'type' => 'test_field', 'settings' => array('changeable' => 0, 'unchangeable' => 0));
$field = field_create_field($field);
$field['settings']['changeable']++;
try {
field_update_field($field);
$this->pass(t("A changeable setting can be updated."));
}
catch (FieldException $e) {
$this->fail(t("An unchangeable setting cannot be updated."));
}
$field['settings']['unchangeable']++;
try {
field_update_field($field);
$this->fail(t("An unchangeable setting can be updated."));
}
catch (FieldException $e) {
$this->pass(t("An unchangeable setting cannot be updated."));
}
}
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
/**
* Test that fields are properly marked active or inactive.
*/
function testActive() {
$field_definition = array(
'field_name' => 'field_1',
'type' => 'test_field',
// For this test, we need a storage backend provided by a different
// module than field_test.module.
'storage' => array(
'type' => 'field_sql_storage',
),
);
field_create_field($field_definition);
// Test disabling and enabling:
// - the field type module,
// - the storage module,
// - both.
$this->_testActiveHelper($field_definition, array('field_test'));
$this->_testActiveHelper($field_definition, array('field_sql_storage'));
$this->_testActiveHelper($field_definition, array('field_test', 'field_sql_storage'));
}
/**
* Helper function for testActive().
*
* Test dependency between a field and a set of modules.
*
* @param $field_definition
* A field definition.
* @param $modules
* An aray of module names. The field will be tested to be inactive as long
* as any of those modules is disabled.
*/
function _testActiveHelper($field_definition, $modules) {
$field_name = $field_definition['field_name'];
// Read the field.
$field = field_read_field($field_name);
$this->assertTrue($field_definition <= $field, t('The field was properly read.'));
module_disable($modules);
$fields = field_read_fields(array('field_name' => $field_name), array('include_inactive' => TRUE));
$this->assertTrue(isset($fields[$field_name]) && $field_definition < $field, t('The field is properly read when explicitly fetching inactive fields.'));
// Re-enable modules one by one, and check that the field is still inactive
// while some modules remain disabled.
while ($modules) {
$field = field_read_field($field_name);
$this->assertTrue(empty($field), t('%modules disabled. The field is marked inactive.', array('%modules' => implode(', ', $modules))));
$module = array_shift($modules);
module_enable(array($module));
}
// Check that the field is active again after all modules have been
// enabled.
$field = field_read_field($field_name);
$this->assertTrue($field_definition <= $field, t('The field was was marked active.'));
}
2009-02-05 03:42:58 +00:00
}
2009-08-26 02:59:22 +00:00
class FieldInstanceCrudTestCase extends FieldTestCase {
2009-02-05 03:42:58 +00:00
protected $field;
2009-03-31 01:49:55 +00:00
public static function getInfo() {
2009-02-05 03:42:58 +00:00
return array(
2009-07-14 02:24:14 +00:00
'name' => 'Field instance CRUD tests',
'description' => 'Create field entities by attaching fields to entities.',
'group' => 'Field',
2009-02-05 03:42:58 +00:00
);
}
function setUp() {
2009-08-09 01:28:06 +00:00
parent::setUp('field_test');
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
2009-04-20 02:46:25 +00:00
$this->field = array(
'field_name' => drupal_strtolower($this->randomName()),
'type' => 'test_field',
);
field_create_field($this->field);
2009-02-05 03:42:58 +00:00
$this->instance_definition = array(
'field_name' => $this->field['field_name'],
'bundle' => FIELD_TEST_BUNDLE,
);
}
// TODO : test creation with
// - a full fledged $instance structure, check that all the values are there
// - a minimal $instance structure, check all default values are set
// defer actual $instance comparison to a helper function, used for the two cases above,
// and for testUpdateFieldInstance
2009-07-14 10:27:29 +00:00
/**
* Test the creation of a field instance.
*/
2009-02-05 03:42:58 +00:00
function testCreateFieldInstance() {
field_create_instance($this->instance_definition);
2009-07-14 10:27:29 +00:00
// Read the raw record from the {field_config_instance} table.
$result = db_query('SELECT * FROM {field_config_instance} WHERE field_name = :field_name AND bundle = :bundle', array(':field_name' => $this->instance_definition['field_name'], ':bundle' => $this->instance_definition['bundle']));
$record = $result->fetchAssoc();
$record['data'] = unserialize($record['data']);
//$instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
2009-02-05 03:42:58 +00:00
$field_type = field_info_field_types($this->field['type']);
2009-07-14 10:27:29 +00:00
$widget_type = field_info_widget_types($field_type['default_widget']);
$formatter_type = field_info_formatter_types($field_type['default_formatter']);
2009-02-05 03:42:58 +00:00
// Check that default values are set.
2009-07-14 10:27:29 +00:00
$this->assertIdentical($record['data']['required'], FALSE, t('Required defaults to false.'));
$this->assertIdentical($record['data']['label'], $this->instance_definition['field_name'], t('Label defaults to field name.'));
$this->assertIdentical($record['data']['description'], '', t('Description defaults to empty string.'));
$this->assertIdentical($record['widget_type'], $field_type['default_widget'], t('Default widget has been written.'));
$this->assertTrue(isset($record['data']['display']['full']), t('Display for "full" build_mode has been written.'));
$this->assertIdentical($record['data']['display']['full']['type'], $field_type['default_formatter'], t('Default formatter for "full" build_mode has been written.'));
// Check that default settings are set.
$this->assertIdentical($record['data']['settings'], $field_type['instance_settings'] , t('Default instance settings have been written.'));
$this->assertIdentical($record['data']['widget']['settings'], $widget_type['settings'] , t('Default widget settings have been written.'));
$this->assertIdentical($record['data']['display']['full']['settings'], $formatter_type['settings'], t('Default formatter settings for "full" build_mode have been written.'));
2009-02-05 03:42:58 +00:00
// Guarantee that the field/bundle combination is unique.
try {
field_create_instance($this->instance_definition);
$this->fail(t('Cannot create two instances with the same field / bundle combination.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot create two instances with the same field / bundle combination.'));
}
// Check that the specified field exists.
try {
$this->instance_definition['field_name'] = $this->randomName();
field_create_instance($this->instance_definition);
$this->fail(t('Cannot create an instance of a non-existing field.'));
}
catch (FieldException $e) {
$this->pass(t('Cannot create an instance of a non-existing field.'));
}
// TODO: test other failures.
}
2009-07-14 10:27:29 +00:00
/**
* Test reading back an instance definition.
*/
2009-02-05 03:42:58 +00:00
function testReadFieldInstance() {
2009-07-14 10:27:29 +00:00
field_create_instance($this->instance_definition);
// Read the instance back.
$instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
2009-08-02 11:24:21 +00:00
$this->assertTrue($this->instance_definition < $instance, t('The field was properly read.'));
2009-02-05 03:42:58 +00:00
}
2009-07-14 10:27:29 +00:00
/**
* Test the update of a field instance.
*/
2009-02-05 03:42:58 +00:00
function testUpdateFieldInstance() {
field_create_instance($this->instance_definition);
$field_type = field_info_field_types($this->field['type']);
// Check that basic changes are saved.
$instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
$instance['required'] = !$instance['required'];
$instance['label'] = $this->randomName();
$instance['description'] = $this->randomName();
$instance['settings']['test_instance_setting'] = $this->randomName();
$instance['widget']['settings']['test_widget_setting'] =$this->randomName();
2009-08-13 01:50:00 +00:00
$instance['widget']['weight']++;
2009-02-05 03:42:58 +00:00
$instance['display']['full']['settings']['test_formatter_setting'] = $this->randomName();
2009-08-13 01:50:00 +00:00
$instance['display']['full']['weight']++;
2009-02-05 03:42:58 +00:00
field_update_instance($instance);
$instance_new = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
$this->assertEqual($instance['required'], $instance_new['required'], t('"required" change is saved'));
$this->assertEqual($instance['label'], $instance_new['label'], t('"label" change is saved'));
$this->assertEqual($instance['description'], $instance_new['description'], t('"description" change is saved'));
$this->assertEqual($instance['widget']['settings']['test_widget_setting'], $instance_new['widget']['settings']['test_widget_setting'], t('Widget setting change is saved'));
2009-08-13 01:50:00 +00:00
$this->assertEqual($instance['widget']['weight'], $instance_new['widget']['weight'], t('Widget weight change is saved'));
2009-02-05 03:42:58 +00:00
$this->assertEqual($instance['display']['full']['settings']['test_formatter_setting'], $instance_new['display']['full']['settings']['test_formatter_setting'], t('Formatter setting change is saved'));
2009-08-13 01:50:00 +00:00
$this->assertEqual($instance['display']['full']['weight'], $instance_new['display']['full']['weight'], t('Widget weight change is saved'));
2009-02-05 03:42:58 +00:00
// Check that changing widget and formatter types updates the default settings.
$instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
$instance['widget']['type'] = 'test_field_widget_multiple';
$instance['display']['full']['type'] = 'field_test_multiple';
field_update_instance($instance);
$instance_new = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
$this->assertEqual($instance['widget']['type'], $instance_new['widget']['type'] , t('Widget type change is saved.'));
$settings = field_info_widget_settings($instance_new['widget']['type']);
$this->assertIdentical($settings, array_intersect_key($instance_new['widget']['settings'], $settings) , t('Widget type change updates default settings.'));
$this->assertEqual($instance['display']['full']['type'], $instance_new['display']['full']['type'] , t('Formatter type change is saved.'));
$info = field_info_formatter_types($instance_new['display']['full']['type']);
$settings = $info['settings'];
$this->assertIdentical($settings, array_intersect_key($instance_new['display']['full']['settings'], $settings) , t('Changing formatter type updates default settings.'));
// Check that adding a new build mode is saved and gets default settings.
$instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
$instance['display']['teaser'] = array();
field_update_instance($instance);
$instance_new = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
$this->assertTrue(isset($instance_new['display']['teaser']), t('Display for the new build_mode has been written.'));
$this->assertIdentical($instance_new['display']['teaser']['type'], $field_type['default_formatter'], t('Default formatter for the new build_mode has been written.'));
$info = field_info_formatter_types($instance_new['display']['teaser']['type']);
$settings = $info['settings'];
$this->assertIdentical($settings, $instance_new['display']['teaser']['settings'] , t('Default formatter settings for the new build_mode have been written.'));
// TODO: test failures.
}
2009-07-14 10:27:29 +00:00
/**
* Test the deletion of a field instance.
*/
2009-02-05 03:42:58 +00:00
function testDeleteFieldInstance() {
// TODO: Test deletion of the data stored in the field also.
// Need to check that data for a 'deleted' field / instance doesn't get loaded
// Need to check data marked deleted is cleaned on cron (not implemented yet...)
// Create two instances for the same field so we can test that only one
// is deleted.
field_create_instance($this->instance_definition);
$this->another_instance_definition = $this->instance_definition;
$this->another_instance_definition['bundle'] .= '_another_bundle';
field_create_instance($this->another_instance_definition);
// Test that the first instance is not deleted, and then delete it.
$instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE));
$this->assertTrue(!empty($instance) && empty($instance['deleted']), t('A new field instance is not marked for deletion.'));
field_delete_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
// Make sure the instance is marked as deleted when the instance is
// specifically loaded.
$instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE));
$this->assertTrue(!empty($instance['deleted']), t('A deleted field instance is marked for deletion.'));
// Try to load the instance normally and make sure it does not show up.
$instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
$this->assertTrue(empty($instance), t('A deleted field instance is not loaded by default.'));
// Make sure the other field instance is not deleted.
$another_instance = field_read_instance($this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']);
$this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('A non-deleted field instance is not marked for deletion.'));
}
}
2009-08-11 14:59:40 +00:00
2009-08-22 00:58:55 +00:00
/**
* Unit test class for the multilanguage fields logic.
*
* The following tests will check the multilanguage logic of _field_invoke() and
* that only the correct values are returned by
* field_multilingual_available_languages().
*/
2009-08-26 02:59:22 +00:00
class FieldTranslationsTestCase extends FieldTestCase {
2009-08-22 00:58:55 +00:00
public static function getInfo() {
return array(
'name' => 'Field translations tests',
'description' => 'Test multilanguage fields logic.',
'group' => 'Field',
);
}
function setUp() {
parent::setUp('locale', 'field_test');
$this->field_name = drupal_strtolower($this->randomName() . '_field_name');
$this->obj_type = 'test_entity';
2009-09-29 17:36:29 +00:00
$field = array(
2009-08-22 00:58:55 +00:00
'field_name' => $this->field_name,
'type' => 'test_field',
'cardinality' => 4,
'translatable' => TRUE,
'settings' => array(
'test_hook_in' => FALSE,
),
);
2009-09-29 17:36:29 +00:00
field_create_field($field);
$this->field = field_read_field($this->field_name);
2009-08-22 00:58:55 +00:00
2009-09-29 17:36:29 +00:00
$instance = array(
2009-08-22 00:58:55 +00:00
'field_name' => $this->field_name,
'bundle' => 'test_bundle',
'label' => $this->randomName() . '_label',
'description' => $this->randomName() . '_description',
'weight' => mt_rand(0, 127),
'settings' => array(
'test_instance_setting' => $this->randomName(),
),
'widget' => array(
'type' => 'test_field_widget',
'label' => 'Test Field',
'settings' => array(
'test_widget_setting' => $this->randomName(),
),
),
);
2009-09-29 17:36:29 +00:00
field_create_instance($instance);
$this->instance = field_read_instance($this->field_name, 'test_bundle');
2009-08-22 00:58:55 +00:00
for ($i = 0; $i < 3; ++$i) {
locale_inc_callback('locale_add_language', 'l' . $i, $this->randomString(), $this->randomString());
}
}
/**
* Ensure that only valid values are returned by field_multilingual_available_languages().
*/
function testFieldAvailableLanguages() {
// Test 'translatable' fieldable info.
$field = $this->field;
$field['field_name'] .= '_untranslatable';
$langcode = language_default();
$suggested_languages = array($langcode->language);
$available_languages = field_multilingual_available_languages($this->obj_type, $field);
$this->assertTrue(count($available_languages) == 1 && $available_languages[0] === FIELD_LANGUAGE_NONE, t('Untranslatable entity: suggested language ignored.'));
// Enable field translations for the entity.
2009-08-31 05:35:47 +00:00
field_test_entity_info_translatable('test_entity', TRUE);
2009-08-22 00:58:55 +00:00
// Test hook_field_languages() invocation on a translatable field.
$this->field['settings']['test_hook_in'] = TRUE;
$enabled_languages = array_keys(language_list());
$available_languages = field_multilingual_available_languages($this->obj_type, $this->field);
$this->assertTrue(in_array(FIELD_LANGUAGE_NONE, $available_languages), t('%language is an available language.', array('%language' => FIELD_LANGUAGE_NONE)));
foreach ($available_languages as $delta => $langcode) {
if ($langcode != FIELD_LANGUAGE_NONE) {
$this->assertTrue(in_array($langcode, $enabled_languages), t('%language is an enabled language.', array('%language' => $langcode)));
}
}
$this->assertFalse(in_array('xx', $available_languages), t('No invalid language was made available.'));
$this->assertTrue(count($available_languages) == count($enabled_languages), t('An enabled language was successfully made unavailable.'));
// Test field_multilingual_available_languages() behavior for untranslatable fields.
$this->field['translatable'] = FALSE;
$this->field_name = $this->field['field_name'] = $this->instance['field_name'] = drupal_strtolower($this->randomName() . '_field_name');
$available_languages = field_multilingual_available_languages($this->obj_type, $this->field);
$this->assertTrue(count($available_languages) == 1 && $available_languages[0] === FIELD_LANGUAGE_NONE, t('For untranslatable fields only neutral language is available.'));
// Test language suggestions.
$this->field['settings']['test_hook_in'] = FALSE;
$this->field['translatable'] = TRUE;
$this->field_name = $this->field['field_name'] = $this->instance['field_name'] = drupal_strtolower($this->randomName() . '_field_name');
$suggested_languages = array();
$lang_count = mt_rand(1, count($enabled_languages) - 1);
for ($i = 0; $i < $lang_count; ++$i) {
do {
$langcode = $enabled_languages[mt_rand(0, $lang_count)];
}
while (in_array($langcode, $suggested_languages));
$suggested_languages[] = $langcode;
}
$available_languages = field_multilingual_available_languages($this->obj_type, $this->field, $suggested_languages);
$this->assertEqual(count($available_languages), count($suggested_languages), t('Suggested languages were successfully made available.'));
foreach ($available_languages as $langcode) {
$this->assertTrue(in_array($langcode, $available_languages), t('Suggested language %language is available.', array('%language' => $langcode)));
}
$this->field_name = $this->field['field_name'] = $this->instance['field_name'] = drupal_strtolower($this->randomName() . '_field_name');
$suggested_languages = array('xx');
$available_languages = field_multilingual_available_languages($this->obj_type, $this->field, $suggested_languages);
$this->assertTrue(empty($available_languages), t('An invalid suggested language was not made available.'));
}
/**
* Test the multilanguage logic of _field_invoke().
*/
function testFieldInvoke() {
$entity_type = 'test_entity';
$entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
// Populate some extra languages to check if _field_invoke() correctly uses
// the result of field_multilingual_available_languages().
$values = array();
$extra_languages = mt_rand(1, 4);
$languages = $available_languages = field_multilingual_available_languages($this->obj_type, $this->field);
for ($i = 0; $i < $extra_languages; ++$i) {
$languages[] = $this->randomString(2);
}
// For each given language provide some random values.
foreach ($languages as $langcode) {
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
$values[$langcode][$delta]['value'] = mt_rand(1, 127);
}
}
$entity->{$this->field_name} = $values;
$results = _field_invoke('test_op', $entity_type, $entity);
foreach ($results as $langcode => $result) {
$hash = md5(serialize(array($entity_type, $entity, $this->field_name, $langcode, $values[$langcode])));
// Check whether the parameters passed to _field_invoke() were correctly
// forwarded to the callback function.
$this->assertEqual($hash, $result, t('The result for %language is correctly stored.', array('%language' => $langcode)));
}
$this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed.'));
}
/**
* Test the multilanguage logic of _field_invoke_multiple().
*/
function testFieldInvokeMultiple() {
$values = array();
$entities = array();
$entity_type = 'test_entity';
$entity_count = mt_rand(1, 5);
$available_languages = field_multilingual_available_languages($this->obj_type, $this->field);
for ($id = 1; $id <= $entity_count; ++$id) {
$entity = field_test_create_stub_entity($id, $id, $this->instance['bundle']);
$languages = $available_languages;
// Populate some extra languages to check whether _field_invoke()
// correctly uses the result of field_multilingual_available_languages().
$extra_languages = mt_rand(1, 4);
for ($i = 0; $i < $extra_languages; ++$i) {
$languages[] = $this->randomString(2);
}
// For each given language provide some random values.
foreach ($languages as $langcode) {
for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
$values[$id][$langcode][$delta]['value'] = mt_rand(1, 127);
}
}
$entity->{$this->field_name} = $values[$id];
$entities[$id] = $entity;
}
$grouped_results = _field_invoke_multiple('test_op_multiple', $entity_type, $entities);
foreach ($grouped_results as $id => $results) {
foreach ($results as $langcode => $result) {
$hash = md5(serialize(array($entity_type, $entities[$id], $this->field_name, $langcode, $values[$id][$langcode])));
// Check whether the parameters passed to _field_invoke() were correctly
// forwarded to the callback function.
$this->assertEqual($hash, $result, t('The result for object %id/%language is correctly stored.', array('%id' => $id, '%language' => $langcode)));
}
$this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed for object %id.', array('%id' => $id)));
}
}
/**
* Test translatable fields storage/retrieval.
*/
function testTranslatableFieldSaveLoad() {
// Enable field translations for nodes.
2009-08-31 05:35:47 +00:00
field_test_entity_info_translatable('node', TRUE);
2009-08-22 00:58:55 +00:00
$obj_info = field_info_fieldable_types('node');
$this->assertTrue(count($obj_info['translation_handlers']), t('Nodes are translatable.'));
// Prepare the field translations.
$eid = $evid = 1;
$obj_type = 'test_entity';
$object = field_test_create_stub_entity($eid, $evid, $this->instance['bundle']);
$field_translations = array();
2009-09-29 17:36:29 +00:00
$available_languages = field_multilingual_available_languages($obj_type, $this->field);
$this->assertTrue(count($available_languages) > 1, t('Field is translatable.'));
foreach ($available_languages as $langcode) {
2009-08-27 01:02:13 +00:00
$field_translations[$langcode] = $this->_generateTestFieldValues($this->field['cardinality']);
2009-08-22 00:58:55 +00:00
}
// Save and reload the field translations.
$object->{$this->field_name} = $field_translations;
field_attach_insert($obj_type, $object);
unset($object->{$this->field_name});
field_attach_load($obj_type, array($eid => $object));
// Check if the correct values were saved/loaded.
foreach ($field_translations as $langcode => $items) {
$result = TRUE;
foreach ($items as $delta => $item) {
$result = $result && $item['value'] == $object->{$this->field_name}[$langcode][$delta]['value'];
}
$this->assertTrue($result, t('%language translation correctly handled.', array('%language' => $langcode)));
}
}
}
2009-08-11 14:59:40 +00:00
/**
* Unit test class for field bulk delete and batch purge functionality.
*/
2009-08-26 02:59:22 +00:00
class FieldBulkDeleteTestCase extends FieldTestCase {
2009-08-11 14:59:40 +00:00
protected $field;
public static function getInfo() {
return array(
'name' => 'Field bulk delete tests',
'description'=> 'Bulk delete fields and instances, and clean up afterwards.',
'group' => 'Field',
);
}
/**
* Convenience function for Field API tests.
*
* Given an array of potentially fully-populated objects and an
* optional field name, generate an array of stub objects of the
* same fieldable type which contains the data for the field name
* (if given).
*
* @param $obj_type
* The entity type of $objects.
* @param $objects
* An array of objects of type $obj_type.
* @param $field_name
* Optional; a field name whose data should be copied from
* $objects into the returned stub objects.
* @return
* An array of stub objects corresponding to $objects.
*/
function _generateStubObjects($obj_type, $objects, $field_name = NULL) {
$stubs = array();
foreach ($objects as $obj) {
2009-09-10 22:31:58 +00:00
$stub = field_create_stub_entity($obj_type, field_extract_ids($obj_type, $obj));
2009-08-11 14:59:40 +00:00
if (isset($field_name)) {
$stub->{$field_name} = $obj->{$field_name};
}
$stubs[] = $stub;
}
return $stubs;
}
function setUp() {
parent::setUp('field_test');
// Clean up data from previous test cases.
$this->fields = array();
$this->instances = array();
// Create two bundles.
$this->bundles = array('bb_1' => 'bb_1', 'bb_2' => 'bb_2');
foreach ($this->bundles as $name => $desc) {
field_test_create_bundle($name, $desc);
}
// Create two fields.
$field = array('field_name' => 'bf_1', 'type' => 'test_field', 'cardinality' => 1);
$this->fields[] = field_create_field($field);
$field = array('field_name' => 'bf_2', 'type' => 'test_field', 'cardinality' => 4);
$this->fields[] = field_create_field($field);
// For each bundle, create an instance of each field, and 10
// objects with values for each field.
$id = 0;
$this->entity_type = 'test_entity';
foreach ($this->bundles as $bundle) {
foreach ($this->fields as $field) {
$instance = array(
'field_name' => $field['field_name'],
'bundle' => $bundle,
'widget' => array(
'type' => 'test_field_widget',
)
);
$this->instances[] = field_create_instance($instance);
}
for ($i = 0; $i < 10; $i++) {
$entity = field_test_create_stub_entity($id, $id, $bundle);
foreach ($this->fields as $field) {
2009-08-22 00:58:55 +00:00
$entity->{$field['field_name']}[FIELD_LANGUAGE_NONE] = $this->_generateTestFieldValues($field['cardinality']);
2009-08-11 14:59:40 +00:00
}
$this->entities[$id] = $entity;
field_attach_insert($this->entity_type, $entity);
$id++;
}
}
}
/**
* Verify that deleting an instance leaves the field data items in
* the database and that the appropriate Field API functions can
* operate on the deleted data and instance.
*
* This tests how field_attach_query() interacts with
* field_delete_instance() and could be moved to FieldCrudTestCase,
* but depends on this class's setUp().
*/
function testDeleteFieldInstance() {
$bundle = reset($this->bundles);
$field = reset($this->fields);
// There are 10 objects of this bundle.
$found = field_attach_query($field['id'], array(array('bundle', $bundle)), FIELD_QUERY_NO_LIMIT);
$this->assertEqual(count($found['test_entity']), 10, 'Correct number of objects found before deleting');
// Delete the instance.
field_delete_instance($field['field_name'], $bundle);
// The instance still exists, deleted.
$instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1));
$this->assertEqual(count($instances), 1, 'There is one deleted instance');
$this->assertEqual($instances[0]['bundle'], $bundle, 'The deleted instance is for the correct bundle');
// There are 0 objects of this bundle with non-deleted data.
$found = field_attach_query($field['id'], array(array('bundle', $bundle)), FIELD_QUERY_NO_LIMIT);
$this->assertTrue(!isset($found['test_entity']), 'No objects found after deleting');
// There are 10 objects of this bundle when deleted fields are allowed, and
// their values are correct.
$found = field_attach_query($field['id'], array(array('bundle', $bundle), array('deleted', 1)), FIELD_QUERY_NO_LIMIT);
field_attach_load($this->entity_type, $found[$this->entity_type], FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1));
$this->assertEqual(count($found['test_entity']), 10, 'Correct number of objects found after deleting');
foreach ($found['test_entity'] as $id => $obj) {
$this->assertEqual($this->entities[$id]->{$field['field_name']}, $obj->{$field['field_name']}, "Object $id with deleted data loaded correctly");
}
}
/**
* Verify that field data items and instances are purged when an
* instance is deleted.
*/
function testPurgeInstance() {
field_test_memorize();
$bundle = reset($this->bundles);
$field = reset($this->fields);
// Delete the instance.
field_delete_instance($field['field_name'], $bundle);
// No field hooks were called.
$mem = field_test_memorize();
$this->assertEqual(count($mem), 0, 'No field hooks were called');
$batch_size = 2;
for ($count = 8; $count >= 0; $count -= 2) {
// Purge two objects.
field_purge_batch($batch_size);
// There are $count deleted objects left.
$found = field_attach_query($field['id'], array(array('bundle', $bundle), array('deleted', 1)), FIELD_QUERY_NO_LIMIT);
$this->assertEqual($count ? count($found['test_entity']) : count($found), $count, 'Correct number of objects found after purging 2');
}
// hook_field_delete() was called on a pseudo-object for each object. Each
// pseudo object has a $field property that matches the original object,
// but no others.
$mem = field_test_memorize();
$this->assertEqual(count($mem['field_test_field_delete']), 10, 'hook_field_delete was called for the right number of objects');
$stubs = $this->_generateStubObjects($this->entity_type, $this->entities, $field['field_name']);
$count = count($stubs);
foreach ($mem['field_test_field_delete'] as $args) {
$obj = $args[1];
$this->assertEqual($stubs[$obj->ftid], $obj, 'hook_field_delete() called with the correct stub');
unset($stubs[$obj->ftid]);
}
$this->assertEqual(count($stubs), $count-10, 'hook_field_delete was called with each object once');
// The instance still exists, deleted.
$instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1));
$this->assertEqual(count($instances), 1, 'There is one deleted instance');
// Purge the instance.
field_purge_batch($batch_size);
// The instance is gone.
$instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1));
$this->assertEqual(count($instances), 0, 'The instance is gone');
// The field still exists, not deleted, because it has a second instance.
$fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1));
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
$this->assertTrue(isset($fields[$field['id']]), 'The field exists and is not deleted');
2009-08-11 14:59:40 +00:00
}
/**
* Verify that fields are preserved and purged correctly as multiple
* instances are deleted and purged.
*/
function testPurgeField() {
$field = reset($this->fields);
foreach ($this->bundles as $bundle) {
// Delete the instance.
field_delete_instance($field['field_name'], $bundle);
// Purge the data.
field_purge_batch(10);
// Purge again to purge the instance.
field_purge_batch(0);
// The field still exists, not deleted, because it was never deleted.
$fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1));
- Patch #443422 by yched, bjaspan | chx, merlinofchaos, Scott Reynolds, plach, profix898, mattyoung: added support for pluggable 'per field' storage engine. Comes with documentation and tests.
The Field Attach API uses the Field Storage API to perform all "database access". Each Field Storage API hook function defines a primitive database operation such as read, write, or delete. The default field storage module, field_sql_storage.module, uses the local SQL database to implement these operations, but alternative field storage backends can choose to represent the data in SQL differently or use a completely different storage mechanism such as a cloud-based database.
2009-09-27 12:52:55 +00:00
$this->assertTrue(isset($fields[$field['id']]), 'The field exists and is not deleted');
2009-08-11 14:59:40 +00:00
}
// Delete the field.
field_delete_field($field['field_name']);
// The field still exists, deleted.
$fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1));
$this->assertEqual($fields[$field['id']]['deleted'], 1, 'The field exists and is deleted');
// Purge the field.
field_purge_batch(0);
// The field is gone.
$fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1));
$this->assertEqual(count($fields), 0, 'The field is purged.');
}
}