field.test 148 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_NOT_SPECIFIED;
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_NOT_SPECIFIED;
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_NOT_SPECIFIED;
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_NOT_SPECIFIED;
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_NOT_SPECIFIED;
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_NOT_SPECIFIED;
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_NOT_SPECIFIED;
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_NOT_SPECIFIED;
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_NOT_SPECIFIED;
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
  /**
   * Tests the 'multiple entity' behavior of field_attach_prepare_view().
   */
  function testFieldAttachPrepareViewMultiple() {
    $entity_type = 'test_entity';
770
    $langcode = LANGUAGE_NOT_SPECIFIED;
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

    // 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_NOT_SPECIFIED;
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_NOT_SPECIFIED;
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_NOT_SPECIFIED;
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_NOT_SPECIFIED;
1004
1005
    // Pretend the form has been built.
    drupal_prepare_form('field_test_entity_form', $form, $form_state);
1006
1007
    drupal_process_form('field_test_entity_form', $form, $form_state);
    $form_state['values'][$this->field_name][$langcode] = $values;
1008
1009
1010
1011
1012
1013
1014
1015
1016
    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']);
      }
    }
1017
    $this->assertIdentical($entity->{$this->field_name}[$langcode], $expected_values, 'Submit filters empty values');
1018
1019
1020
  }
}

1021
class FieldInfoTestCase extends FieldTestCase {
1022

1023
  public static function getInfo() {
1024
    return array(
1025
1026
      'name' => 'Field info tests',
      'description' => 'Get information about existing fields, instances and bundles.',
1027
      'group' => 'Field API',
1028
1029
1030
1031
    );
  }

  function setUp() {
1032
    parent::setUp('field_test');
1033
1034
  }

1035
1036
1037
  /**
   * Test that field types and field definitions are correcly cached.
   */
1038
1039
1040
  function testFieldInfo() {
    // Test that field_test module's fields, widgets, and formatters show up.

1041
1042
1043
1044
1045
    $field_test_info = field_test_field_info();
    // We need to account for the existence of user_field_info_alter().
    foreach (array_keys($field_test_info) as $name) {
      $field_test_info[$name]['instance_settings']['user_register_form'] = FALSE;
    }
1046
1047
1048
    $info = field_info_field_types();
    foreach ($field_test_info as $t_key => $field_type) {
      foreach ($field_type as $key => $val) {
1049
        $this->assertEqual($info[$t_key][$key], $val, t("Field type $t_key key $key is $val"));
1050
      }
1051
      $this->assertEqual($info[$t_key]['module'], 'field_test',  t("Field type field_test module appears"));
1052
1053
    }

1054
    $formatter_info = field_test_field_formatter_info();