field.test 144 KB
Newer Older
1 2
<?php

3 4
/**
 * @file
5
 * Tests for field.module.
6 7 8
 */

/**
9
 * Parent class for Field API tests.
10
 */
11
class FieldTestCase extends DrupalWebTestCase {
12 13 14 15 16
  var $default_storage = 'field_sql_storage';

  /**
   * Set the default field storage backend for fields created during tests.
   */
17
  function setUp() {
18 19 20
    // Since this is a base class for many test cases, support the same
    // flexibility that DrupalWebTestCase::setUp() has for the modules to be
    // passed in as either an array or a variable number of string arguments.
21 22 23
    $modules = func_get_args();
    if (isset($modules[0]) && is_array($modules[0])) {
      $modules = $modules[0];
24 25
    }
    parent::setUp($modules);
26 27 28
    // Set default storage backend.
    variable_set('field_storage_default', $this->default_storage);
  }
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44

  /**
   * 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;
45
  }
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66

  /**
   * Assert that a field has the expected values in an entity.
   *
   * This function only checks a single column in the field values.
   *
   * @param $entity
   *   The entity to test.
   * @param $field_name
   *   The name of the field to test
   * @param $langcode
   *   The language code for the values.
   * @param $expected_values
   *   The array of expected values.
   * @param $column
   *   (Optional) the name of the column to check.
   */
  function assertFieldValues($entity, $field_name, $langcode, $expected_values, $column = 'value') {
    $e = clone $entity;
    field_attach_load('test_entity', array($e->ftid => $e));
    $values = isset($e->{$field_name}[$langcode]) ? $e->{$field_name}[$langcode] : array();
67
    $this->assertEqual(count($values), count($expected_values), t('Expected number of values were saved.'));
68
    foreach ($expected_values as $key => $value) {
69
      $this->assertEqual($values[$key][$column], $value, t('Value @value was saved correctly.', array('@value' => $value)));
70 71
    }
  }
72
}
73

74
class FieldAttachTestCase extends FieldTestCase {
75
  function setUp() {
76 77 78
    // Since this is a base class for many test cases, support the same
    // flexibility that DrupalWebTestCase::setUp() has for the modules to be
    // passed in as either an array or a variable number of string arguments.
79 80 81
    $modules = func_get_args();
    if (isset($modules[0]) && is_array($modules[0])) {
      $modules = $modules[0];
82 83 84 85 86
    }
    if (!in_array('field_test', $modules)) {
      $modules[] = 'field_test';
    }
    parent::setUp($modules);
87

88
    $this->field_name = drupal_strtolower($this->randomName() . '_field_name');
89
    $this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4);
90 91
    $this->field = field_create_field($this->field);
    $this->field_id = $this->field['id'];
92 93
    $this->instance = array(
      'field_name' => $this->field_name,
94
      'entity_type' => 'test_entity',
95
      'bundle' => 'test_bundle',
96 97
      'label' => $this->randomName() . '_label',
      'description' => $this->randomName() . '_description',
98 99 100 101 102 103 104 105 106 107 108 109 110 111
      '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);
  }
112 113 114 115 116 117 118 119 120 121 122 123 124
}

/**
 * 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.',
125
      'group' => 'Field API',
126 127
    );
  }
128

129
  /**
130
   * Check field values insert, update and load.
131
   *
132 133
   * Works independently of the underlying field storage backend. Inserts or
   * updates random field data and then loads and verifies the data.
134
   */
135
  function testFieldAttachSaveLoad() {
136 137 138 139
    // 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);
140
    $langcode = LANGUAGE_NONE;
141

142 143 144
    $entity_type = 'test_entity';
    $values = array();

145
    // TODO : test empty values filtering and "compression" (store consecutive deltas).
146 147

    // Preparation: create three revisions and store them in $revision array.
148 149
    for ($revision_id = 0; $revision_id < 3; $revision_id++) {
      $revision[$revision_id] = field_test_create_stub_entity(0, $revision_id, $this->instance['bundle']);
150 151
      // Note: we try to insert one extra value.
      $values[$revision_id] = $this->_generateTestFieldValues($this->field['cardinality'] + 1);
152
      $current_revision = $revision_id;
153
      // If this is the first revision do an insert.
154
      if (!$revision_id) {
155
        $revision[$revision_id]->{$this->field_name}[$langcode] = $values[$revision_id];
156
        field_attach_insert($entity_type, $revision[$revision_id]);
157 158
      }
      else {
159
        // Otherwise do an update.
160
        $revision[$revision_id]->{$this->field_name}[$langcode] = $values[$revision_id];
161
        field_attach_update($entity_type, $revision[$revision_id]);
162 163
      }
    }
164 165 166 167

    // 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));
168
    // Number of values per field loaded equals the field cardinality.
169
    $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], t('Current revision: expected number of values'));
170 171
    for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
      // The field value loaded matches the one inserted or updated.
172
      $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)));
173
      // The value added in hook_field_load() is found.
174
      $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)));
175 176 177 178
    }

    // Confirm each revision loads the correct data.
    foreach (array_keys($revision) as $revision_id) {
179 180
      $entity = field_test_create_stub_entity(0, $revision_id, $this->instance['bundle']);
      field_attach_load_revision($entity_type, array(0 => $entity));
181
      // Number of values per field loaded equals the field cardinality.
182
      $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], t('Revision %revision_id: expected number of values.', array('%revision_id' => $revision_id)));
183
      for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
184
        // The field value loaded matches the one inserted or updated.
185
        $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)));
186
        // The value added in hook_field_load() is found.
187
        $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)));
188 189 190 191 192 193 194 195 196
      }
    }
  }

  /**
   * Test the 'multiple' load feature.
   */
  function testFieldAttachLoadMultiple() {
    $entity_type = 'test_entity';
197
    $langcode = LANGUAGE_NONE;
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217

    // 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');
218 219
      $field = field_create_field($field);
      $field_ids[$i] = $field['id'];
220 221 222
      foreach ($field_bundles_map[$i] as $bundle) {
        $instance = array(
          'field_name' => $field_names[$i],
223
          'entity_type' => 'test_entity',
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
          '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]);
239
      $instances = field_info_instances('test_entity', $bundle);
240 241
      foreach ($instances as $field_name => $instance) {
        $values[$index][$field_name] = mt_rand(1, 127);
242
        $entity->$field_name = array($langcode => array(array('value' => $values[$index][$field_name])));
243 244 245 246 247 248 249
      }
      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) {
250
      $instances = field_info_instances($entity_type, $bundles[$index]);
251 252
      foreach ($instances as $field_name => $instance) {
        // The field value loaded matches the one inserted.
253
        $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], $values[$index][$field_name], t('Entity %index: expected value was found.', array('%index' => $index)));
254
        // The value added in hook_field_load() is found.
255
        $this->assertEqual($entity->{$field_name}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => $index)));
256 257
      }
    }
258 259 260

    // Check that the single-field load option works.
    $entity = field_test_create_stub_entity(1, 1, $bundles[1]);
261
    field_attach_load($entity_type, array(1 => $entity), FIELD_LOAD_CURRENT, array('field_id' => $field_ids[1]));
262 263 264 265
    $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)));
    $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])));
266
  }
267

268 269 270 271 272
  /**
   * Test saving and loading fields using different storage backends.
   */
  function testFieldAttachSaveLoadDifferentStorage() {
    $entity_type = 'test_entity';
273
    $langcode = LANGUAGE_NONE;
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293

    // 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'],
294
        'entity_type' => 'test_entity',
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
        '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) {
315
      $this->assertEqual($values[$field['field_name']], $entity->{$field['field_name']}[$langcode], t('%storage storage: expected values were found.', array('%storage' => $field['storage']['type'])));
316 317 318
    }
  }

319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
  /**
   * Test storage details alteration.
   *
   * @see field_test_storage_details_alter()
   */
  function testFieldStorageDetailsAlter() {
    $field_name = 'field_test_change_my_details';
    $field = array(
      'field_name' => $field_name,
      'type' => 'test_field',
      'cardinality' => 4,
      'storage' => array('type' => 'field_test_storage'),
    );
    $field = field_create_field($field);
    $instance = array(
      'field_name' => $field_name,
335
      'entity_type' => 'test_entity',
336 337 338 339 340
      'bundle' => 'test_bundle',
    );
    field_create_instance($instance);

    $field = field_info_field($instance['field_name']);
341
    $instance = field_info_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']);
342 343

    // The storage details are indexed by a storage engine type.
344
    $this->assertTrue(array_key_exists('drupal_variables', $field['storage']['details']), t('The storage type is Drupal variables.'));
345

346
    $details = $field['storage']['details']['drupal_variables'];
347 348 349

    // The field_test storage details are indexed by variable name. The details
    // are altered, so moon and mars are correct for this test.
350 351
    $this->assertTrue(array_key_exists('moon', $details[FIELD_LOAD_CURRENT]), t('Moon is available in the instance array.'));
    $this->assertTrue(array_key_exists('mars', $details[FIELD_LOAD_REVISION]), t('Mars is available in the instance array.'));
352 353 354 355

    // Test current and revision storage details together because the columns
    // are the same.
    foreach ((array) $field['columns'] as $column_name => $attributes) {
356 357
      $this->assertEqual($details[FIELD_LOAD_CURRENT]['moon'][$column_name], $column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'moon[FIELD_LOAD_CURRENT]')));
      $this->assertEqual($details[FIELD_LOAD_REVISION]['mars'][$column_name], $column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'mars[FIELD_LOAD_REVISION]')));
358 359 360
    }
  }

361 362 363
  /**
   * Tests insert and update with missing or NULL fields.
   */
364 365
  function testFieldAttachSaveMissingData() {
    $entity_type = 'test_entity';
366
    $entity_init = field_test_create_stub_entity();
367
    $langcode = LANGUAGE_NONE;
368

369
    // Insert: Field is missing.
370
    $entity = clone($entity_init);
371
    field_attach_insert($entity_type, $entity);
372 373 374

    $entity = clone($entity_init);
    field_attach_load($entity_type, array($entity->ftid => $entity));
375
    $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: missing field results in no value saved'));
376

377
    // Insert: Field is NULL.
378 379
    field_cache_clear();
    $entity = clone($entity_init);
380
    $entity->{$this->field_name} = NULL;
381 382
    field_attach_insert($entity_type, $entity);

383 384
    $entity = clone($entity_init);
    field_attach_load($entity_type, array($entity->ftid => $entity));
385
    $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: NULL field results in no value saved'));
386 387 388 389

    // Add some real data.
    field_cache_clear();
    $entity = clone($entity_init);
390
    $values = $this->_generateTestFieldValues(1);
391
    $entity->{$this->field_name}[$langcode] = $values;
392
    field_attach_insert($entity_type, $entity);
393 394 395

    $entity = clone($entity_init);
    field_attach_load($entity_type, array($entity->ftid => $entity));
396
    $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved'));
397

398
    // Update: Field is missing. Data should survive.
399 400
    field_cache_clear();
    $entity = clone($entity_init);
401
    field_attach_update($entity_type, $entity);
402 403 404

    $entity = clone($entity_init);
    field_attach_load($entity_type, array($entity->ftid => $entity));
405
    $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Update: missing field leaves existing values in place'));
406

407
    // Update: Field is NULL. Data should be wiped.
408 409
    field_cache_clear();
    $entity = clone($entity_init);
410
    $entity->{$this->field_name} = NULL;
411
    field_attach_update($entity_type, $entity);
412 413 414

    $entity = clone($entity_init);
    field_attach_load($entity_type, array($entity->ftid => $entity));
415
    $this->assertTrue(empty($entity->{$this->field_name}), t('Update: NULL field removes existing values'));
416 417 418 419 420 421 422 423 424 425

    // 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));
426
    $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved'));
427 428 429 430 431 432 433 434 435

    // 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));
436
    $this->assertTrue(empty($entity->{$this->field_name}), t('Update: empty array removes existing values'));
437 438 439 440 441 442
  }

  /**
   * Test insert with missing or NULL fields, with default value.
   */
  function testFieldAttachSaveMissingDataDefaultValue() {
443
    // Add a default value function.
444 445 446 447 448
    $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();
449
    $langcode = LANGUAGE_NONE;
450 451 452

    // Insert: Field is NULL.
    $entity = clone($entity_init);
453
    $entity->{$this->field_name}[$langcode] = NULL;
454 455 456 457
    field_attach_insert($entity_type, $entity);

    $entity = clone($entity_init);
    field_attach_load($entity_type, array($entity->ftid => $entity));
458
    $this->assertTrue(empty($entity->{$this->field_name}[$langcode]), t('Insert: NULL field results in no value saved'));
459 460 461 462 463 464 465 466 467

    // 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);
468
    $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Insert: missing field results in default value saved'));
469 470
  }

471 472 473 474 475
  /**
   * Test field_attach_delete().
   */
  function testFieldAttachDelete() {
    $entity_type = 'test_entity';
476
    $langcode = LANGUAGE_NONE;
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
    $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));
498
      $this->assertEqual(count($read->{$this->field_name}[$langcode]), $this->field['cardinality'], "The test entity revision $vid has {$this->field['cardinality']} values.");
499 500 501 502 503 504 505
    }

    // 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));
506
      $this->assertEqual(count($read->{$this->field_name}[$langcode]), $this->field['cardinality'], "The test entity revision $vid has {$this->field['cardinality']} values.");
507 508 509 510 511
    }

    // 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));
512
    $this->assertEqual(count($read->{$this->field_name}[$langcode]), $this->field['cardinality'], "The test entity current revision has {$this->field['cardinality']} values.");
513 514 515 516 517 518

    // 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));
519
      $this->assertIdentical($read->{$this->field_name}, array(), "The test entity revision $vid is deleted.");
520 521 522
    }
    $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']);
    field_attach_load($entity_type, array(0 => $read));
523
    $this->assertIdentical($read->{$this->field_name}, array(), t('The test entity current revision is deleted.'));
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538
  }

  /**
   * 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);

539
    // Save an entity with data in the field.
540
    $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
541
    $langcode = LANGUAGE_NONE;
542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
    $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.
558
    $this->instance = field_info_instance($entity_type, $this->field_name, $new_bundle);
559 560 561
    $this->assertIdentical($this->instance['bundle'], $new_bundle, "Bundle name has been updated in the instance.");

    // Verify the field data is present on load.
562
    $entity = field_test_create_stub_entity(0, 0, $new_bundle);
563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
    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,
586
      'entity_type' => 'test_entity',
587 588 589 590 591 592 593 594 595 596 597
      '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);

598
    // Save an entity with data for both fields
599
    $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
600
    $langcode = LANGUAGE_NONE;
601 602 603
    $values = $this->_generateTestFieldValues($this->field['cardinality']);
    $entity->{$this->field_name}[$langcode] = $values;
    $entity->{$field_name}[$langcode] = $this->_generateTestFieldValues(1);
604
    field_attach_insert('test_entity', $entity);
605 606 607

    // Verify the fields are present on load
    $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
608
    field_attach_load('test_entity', array(0 => $entity));
609 610 611 612 613 614 615 616 617
    $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']);
618
    field_attach_load('test_entity', array(0 => $entity));
619 620 621 622
    $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
623 624
    $this->assertFalse(field_read_instance('test_entity', $this->field_name, $this->instance['bundle']), "First field is deleted");
    $this->assertFalse(field_read_instance('test_entity', $field_name, $instance['bundle']), "Second field is deleted");
625 626
  }
}
627

628 629 630 631 632 633 634 635
/**
 * 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.',
636
      'group' => 'Field API',
637 638 639 640
    );
  }

  /**
641
   * Test field_attach_view() and field_attach_prepare_view().
642
   */
643
  function testFieldAttachView() {
644
    $entity_type = 'test_entity';
645
    $entity_init = field_test_create_stub_entity();
646
    $langcode = LANGUAGE_NONE;
647 648

    // Populate values to be displayed.
649
    $values = $this->_generateTestFieldValues($this->field['cardinality']);
650
    $entity_init->{$this->field_name}[$langcode] = $values;
651 652

    // Simple formatter, label displayed.
653
    $entity = clone($entity_init);
654 655 656 657 658 659 660 661 662 663 664
    $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);
665 666
    field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full');
    $entity->content = field_attach_view($entity_type, $entity, 'full');
667 668 669 670 671 672 673 674 675
    $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.
676
    $entity = clone($entity_init);
677 678
    $this->instance['display']['full']['label'] = 'hidden';
    field_update_instance($this->instance);
679 680
    field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full');
    $entity->content = field_attach_view($entity_type, $entity, 'full');
681 682 683 684 685
    $output = drupal_render($entity->content);
    $this->content = $output;
    $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed.");

    // Field hidden.
686
    $entity = clone($entity_init);
687 688 689 690 691 692 693
    $this->instance['display'] = array(
      'full' => array(
        'label' => 'above',
        'type' => 'hidden',
      ),
    );
    field_update_instance($this->instance);
694 695
    field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full');
    $entity->content = field_attach_view($entity_type, $entity, 'full');
696 697 698 699 700 701 702 703
    $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.
704
    $entity = clone($entity_init);
705 706 707 708 709 710 711 712 713 714 715
    $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);
716 717
    field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full');
    $entity->content = field_attach_view($entity_type, $entity, 'full');
718 719 720 721 722 723 724 725
    $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.");

726
    // Test a formatter that uses hook_field_formatter_prepare_view().
727 728 729 730 731
    $entity = clone($entity_init);
    $formatter_setting = $this->randomName();
    $this->instance['display'] = array(
      'full' => array(
        'label' => 'above',
732
        'type' => 'field_test_with_prepare_view',
733 734 735 736 737 738
        'settings' => array(
          'test_formatter_setting_additional' => $formatter_setting,
        )
      ),
    );
    field_update_instance($this->instance);
739 740
    field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full');
    $entity->content = field_attach_view($entity_type, $entity, 'full');
741 742 743 744 745 746 747 748
    $output = drupal_render($entity->content);
    $this->content = $output;
    foreach ($values as $delta => $value) {
      $this->content = $output;
      $expected = $formatter_setting . '|' . $value['value'] . '|' . ($value['value'] + 1);
      $this->assertRaw($expected, "Value $delta is displayed, formatter settings are applied.");
    }

749 750
    // TODO:
    // - check display order with several fields
751 752 753 754 755 756 757 758 759 760 761

    // 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;
      }
    }
762
    $this->assertTrue($result, t('Variable $@field_name correctly populated.', array('@field_name' => $this->field_name)));
763 764
  }

765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
  /**
   * Tests the 'multiple entity' behavior of field_attach_prepare_view().
   */
  function testFieldAttachPrepareViewMultiple() {
    $entity_type = 'test_entity';
    $langcode = LANGUAGE_NONE;

    // Set the instance to be hidden.
    $this->instance['display']['full']['type'] = 'hidden';
    field_update_instance($this->instance);

    // Set up a second instance on another bundle, with a formatter that uses
    // hook_field_formatter_prepare_view().
    field_test_create_bundle('test_bundle_2');
    $formatter_setting = $this->randomName();
    $this->instance2 = $this->instance;
    $this->instance2['bundle'] = 'test_bundle_2';
    $this->instance2['display']['full'] = array(
      'type' => 'field_test_with_prepare_view',
      'settings' => array(
        'test_formatter_setting_additional' => $formatter_setting,
      )
    );
    field_create_instance($this->instance2);

    // Create one entity in each bundle.
    $entity1_init = field_test_create_stub_entity(1, 1, 'test_bundle');
    $values1 = $this->_generateTestFieldValues($this->field['cardinality']);
    $entity1_init->{$this->field_name}[$langcode] = $values1;

    $entity2_init = field_test_create_stub_entity(2, 2, 'test_bundle_2');
    $values2 = $this->_generateTestFieldValues($this->field['cardinality']);
    $entity2_init->{$this->field_name}[$langcode] = $values2;

    // Run prepare_view, and check that the entities come out as expected.
    $entity1 = clone($entity1_init);
    $entity2 = clone($entity2_init);
    field_attach_prepare_view($entity_type, array($entity1->ftid => $entity1, $entity2->ftid => $entity2), 'full');
    $this->assertFalse(isset($entity1->{$this->field_name}[$langcode][0]['additional_formatter_value']), 'Entity 1 did not run through the prepare_view hook.');
    $this->assertTrue(isset($entity2->{$this->field_name}[$langcode][0]['additional_formatter_value']), 'Entity 2 ran through the prepare_view hook.');

    // Same thing, reversed order.
    $entity1 = clone($entity1_init);
    $entity2 = clone($entity2_init);
    field_attach_prepare_view($entity_type, array($entity2->ftid => $entity2, $entity1->ftid => $entity1), 'full');
    $this->assertFalse(isset($entity1->{$this->field_name}[$langcode][0]['additional_formatter_value']), 'Entity 1 did not run through the prepare_view hook.');
    $this->assertTrue(isset($entity2->{$this->field_name}[$langcode][0]['additional_formatter_value']), 'Entity 2 ran through the prepare_view hook.');
  }

814 815 816
  /**
   * Test field cache.
   */
817
  function testFieldAttachCache() {
818
    // Initialize random values and a test entity.
819
    $entity_init = field_test_create_stub_entity(1, 1, $this->instance['bundle']);
820
    $langcode = LANGUAGE_NONE;
821
    $values = $this->_generateTestFieldValues($this->field['cardinality']);
822

823
    // Non-cacheable entity type.
824 825
    $entity_type = 'test_entity';
    $cid = "field:$entity_type:{$entity_init->ftid}";
826

827
    // Check that no initial cache entry is present.
828
    $this->assertFalse(cache('field')->get($cid), t('Non-cached: no initial cache entry'));
829

830
    // Save, and check that no cache entry is present.
831
    $entity = clone($entity_init);
832
    $entity->{$this->field_name}[$langcode] = $values;
833
    field_attach_insert($entity_type, $entity);
834
    $this->assertFalse(cache('field')->get($cid), t('Non-cached: no cache entry on insert'));
835

836
    // Load, and check that no cache entry is present.
837
    $entity = clone($entity_init);
838
    field_attach_load($entity_type, array($entity->ftid => $entity));
839
    $this->assertFalse(cache('field')->get($cid), t('Non-cached: no cache entry on load'));
840

841

842
    // Cacheable entity type.
843 844 845
    $entity_type = 'test_cacheable_entity';
    $cid = "field:$entity_type:{$entity_init->ftid}";
    $instance = $this->instance;
846
    $instance['entity_type'] = $entity_type;
847
    field_create_instance($instance);
848

849
    // Check that no initial cache entry is present.
850
    $this->assertFalse(cache('field')->get($cid), t('Cached: no initial cache entry'));
851

852
    // Save, and check that no cache entry is present.
853
    $entity = clone($entity_init);
854
    $entity->{$this->field_name}[$langcode] = $values;
855
    field_attach_insert($entity_type, $entity);
856
    $this->assertFalse(cache('field')->get($cid), t('Cached: no cache entry on insert'));
857

858 859
    // Load a single field, and check that no cache entry is present.
    $entity = clone($entity_init);
860
    field_attach_load($entity_type, array($entity->ftid => $entity), FIELD_LOAD_CURRENT, array('field_id' => $this->field_id));
861 862
    $cache = cache('field')->get($cid);
    $this->assertFalse(cache('field')->get($cid), t('Cached: no cache entry on loading a single field'));
863

864
    // Load, and check that a cache entry is present with the expected values.
865
    $entity = clone($entity_init);
866
    field_attach_load($entity_type, array($entity->ftid => $entity));
867
    $cache = cache('field')->get($cid);
868
    $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load'));
869

870 871
    // Update with different values, and check that the cache entry is wiped.
    $values = $this->_generateTestFieldValues($this->field['cardinality']);
872
    $entity = clone($entity_init);
873
    $entity->{$this->field_name}[$langcode] = $values;
874
    field_attach_update($entity_type, $entity);
875
    $this->assertFalse(cache('field')->get($cid), t('Cached: no cache entry on update'));
876 877

    // Load, and check that a cache entry is present with the expected values.
878
    $entity = clone($entity_init);
879
    field_attach_load($entity_type, array($entity->ftid => $entity));
880
    $cache = cache('field')->get($cid);
881
    $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load'));
882 883

    // Create a new revision, and check that the cache entry is wiped.
884
    $entity_init = field_test_create_stub_entity(1, 2, $this->instance['bundle']);
885
    $values = $this->_generateTestFieldValues($this->field['cardinality']);
886
    $entity = clone($entity_init);
887
    $entity->{$this->field_name}[$langcode] = $values;
888
    field_attach_update($entity_type, $entity);
889 890
    $cache = cache('field')->get($cid);
    $this->assertFalse(cache('field')->get($cid), t('Cached: no cache entry on new revision creation'));
891 892

    // Load, and check that a cache entry is present with the expected values.
893
    $entity = clone($entity_init);
894
    field_attach_load($entity_type, array($entity->ftid => $entity));
895
    $cache = cache('field')->get($cid);
896
    $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load'));
897 898

    // Delete, and check that the cache entry is wiped.
899
    field_attach_delete($entity_type, $entity);
900
    $this->assertFalse(cache('field')->get($cid), t('Cached: no cache entry after delete'));
901 902
  }

903 904 905 906 907 908
  /**
   * Test field_attach_validate().
   *
   * Verify that field_attach_validate() invokes the correct
   * hook_field_validate.
   */
909 910 911
  function testFieldAttachValidate() {
    $entity_type = 'test_entity';
    $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
912
    $langcode = LANGUAGE_NONE;
913 914 915 916 917 918 919 920

    // Set up values to generate errors
    $values = array();
    for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
      $values[$delta]['value'] = -1;
    }
    // Arrange for item 1 not to generate an error
    $values[1]['value'] = 1;
921
    $entity->{$this->field_name}[$langcode] = $values;
922

923 924 925 926 927 928
    try {
      field_attach_validate($entity_type, $entity);
    }
    catch (FieldValidationException $e) {
      $errors = $e->errors;
    }
929 930 931

    foreach ($values as $delta => $value) {
      if ($value['value'] != 1) {
932 933 934
        $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]);
935 936
      }
      else {
937
        $this->assertFalse(isset($errors[$this->field_name][$langcode][$delta]), "No error set on value $delta");
938 939
      }
    }
940
    $this->assertEqual(count($errors[$this->field_name][$langcode]), 0, 'No extraneous errors set');
941 942 943 944 945 946 947 948 949

    // Check that cardinality is validated.
    $entity->{$this->field_name}[$langcode] = $this->_generateTestFieldValues($this->field['cardinality'] + 1);
    try {
      field_attach_validate($entity_type, $entity);
    }
    catch (FieldValidationException $e) {
      $errors = $e->errors;
    }
950
    $this->assertEqual($errors[$this->field_name][$langcode][0][0]['error'], 'field_cardinality', t('Cardinality validation failed.'));
951

952 953
  }

954 955 956 957 958 959
  /**
   * Test field_attach_form().
   *
   * This could be much more thorough, but it does verify that the correct
   * widgets show up.
   */
960 961 962 963
  function testFieldAttachForm() {
    $entity_type = 'test_entity';
    $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);

964 965
    $form = array();
    $form_state = form_state_defaults();
966 967
    field_attach_form($entity_type, $entity, $form, $form_state);

968
    $langcode = LANGUAGE_NONE;
969
    $this->assertEqual($form[$this->field_name][$langcode]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}");
970 971
    for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
      // field_test_widget uses 'textfield'
972
      $this->assertEqual($form[$this->field_name][$langcode][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield");
973 974 975
    }
  }

976 977 978
  /**
   * Test field_attach_submit().
   */
979 980 981 982 983
  function testFieldAttachSubmit() {
    $entity_type = 'test_entity';
    $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);

    // Build the form.
984 985
    $form = array();
    $form_state = form_state_defaults();
986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002
    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;

1003
    $langcode = LANGUAGE_NONE;
1004 1005
    // Pretend the form has been built.
    drupal_prepare_form('field_test_entity_form', $form, $form_state);