/[drupal]/drupal/modules/field/field.test
ViewVC logotype

Contents of /drupal/modules/field/field.test

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.65 - (show annotations) (download) (as text)
Sat Oct 31 16:06:35 2009 UTC (3 weeks, 4 days ago) by dries
Branch: MAIN
CVS Tags: DRUPAL-7-0-UNSTABLE-10
Changes since 1.64: +6 -6 lines
File MIME type: text/x-php
- Patch #606994 by yched: move entity handling out of Field API.
1 <?php
2 // $Id: field.test,v 1.64 2009/10/22 00:49:12 dries Exp $
3
4 /**
5 * @file
6 * Unit test file for fields in core.
7 */
8
9 /**
10 * Parent class for Field API tests.
11 */
12 class FieldTestCase extends DrupalWebTestCase {
13 var $default_storage = 'field_sql_storage';
14
15 /**
16 * Set the default field storage backend for fields created during tests.
17 */
18 function setUp() {
19 // Call parent::setUp().
20 $args = func_get_args();
21 call_user_func_array(array('parent', 'setUp'), $args);
22 // Set default storage backend.
23 variable_set('field_storage_default', $this->default_storage);
24 }
25
26 /**
27 * Generate random values for a field_test field.
28 *
29 * @param $cardinality
30 * Number of values to generate.
31 * @return
32 * An array of random values, in the format expected for field values.
33 */
34 function _generateTestFieldValues($cardinality) {
35 $values = array();
36 for ($i = 0; $i < $cardinality; $i++) {
37 // field_test fields treat 0 as 'empty value'.
38 $values[$i]['value'] = mt_rand(1, 127);
39 }
40 return $values;
41 }
42 }
43
44 class FieldAttachTestCase extends FieldTestCase {
45 function setUp() {
46 parent::setUp('field_test');
47
48 $this->field_name = drupal_strtolower($this->randomName() . '_field_name');
49 $this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4);
50 $this->field = field_create_field($this->field);
51 $this->field_id = $this->field['id'];
52 $this->instance = array(
53 'field_name' => $this->field_name,
54 'object_type' => 'test_entity',
55 'bundle' => 'test_bundle',
56 'label' => $this->randomName() . '_label',
57 'description' => $this->randomName() . '_description',
58 'weight' => mt_rand(0, 127),
59 'settings' => array(
60 'test_instance_setting' => $this->randomName(),
61 ),
62 'widget' => array(
63 'type' => 'test_field_widget',
64 'label' => 'Test Field',
65 'settings' => array(
66 'test_widget_setting' => $this->randomName(),
67 )
68 )
69 );
70 field_create_instance($this->instance);
71 }
72 }
73
74 /**
75 * Unit test class for storage-related field_attach_* functions.
76 *
77 * All field_attach_* test work with all field_storage plugins and
78 * all hook_field_attach_pre_{load,insert,update}() hooks.
79 */
80 class FieldAttachStorageTestCase extends FieldAttachTestCase {
81 public static function getInfo() {
82 return array(
83 'name' => 'Field attach tests (storage-related)',
84 'description' => 'Test storage-related Field Attach API functions.',
85 'group' => 'Field',
86 );
87 }
88
89 /**
90 * Check field values insert, update and load.
91 *
92 * Works independently of the underlying field storage backend. Inserts or
93 * updates random field data and then loads and verifies the data.
94 */
95 function testFieldAttachSaveLoad() {
96 // Configure the instance so that we test hook_field_load() (see
97 // field_test_field_load() in field_test.module).
98 $this->instance['settings']['test_hook_field_load'] = TRUE;
99 field_update_instance($this->instance);
100 $langcode = FIELD_LANGUAGE_NONE;
101
102 $entity_type = 'test_entity';
103 $values = array();
104
105 // TODO : test empty values filtering and "compression" (store consecutive deltas).
106
107 // Preparation: create three revisions and store them in $revision array.
108 for ($revision_id = 0; $revision_id < 3; $revision_id++) {
109 $revision[$revision_id] = field_test_create_stub_entity(0, $revision_id, $this->instance['bundle']);
110 // Note: we try to insert one extra value.
111 $values[$revision_id] = $this->_generateTestFieldValues($this->field['cardinality'] + 1);
112 $current_revision = $revision_id;
113 // If this is the first revision do an insert.
114 if (!$revision_id) {
115 $revision[$revision_id]->{$this->field_name}[$langcode] = $values[$revision_id];
116 field_attach_insert($entity_type, $revision[$revision_id]);
117 }
118 else {
119 // Otherwise do an update.
120 $revision[$revision_id]->{$this->field_name}[$langcode] = $values[$revision_id];
121 field_attach_update($entity_type, $revision[$revision_id]);
122 }
123 }
124
125 // Confirm current revision loads the correct data.
126 $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
127 field_attach_load($entity_type, array(0 => $entity));
128 // Number of values per field loaded equals the field cardinality.
129 $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], t('Current revision: expected number of values'));
130 for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
131 // The field value loaded matches the one inserted or updated.
132 $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)));
133 // The value added in hook_field_load() is found.
134 $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)));
135 }
136
137 // Confirm each revision loads the correct data.
138 foreach (array_keys($revision) as $revision_id) {
139 $entity = field_test_create_stub_entity(0, $revision_id, $this->instance['bundle']);
140 field_attach_load_revision($entity_type, array(0 => $entity));
141 // Number of values per field loaded equals the field cardinality.
142 $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], t('Revision %revision_id: expected number of values.', array('%revision_id' => $revision_id)));
143 for ($delta = 0; $delta < $this->field['cardinality']; $delta++) {
144 // The field value loaded matches the one inserted or updated.
145 $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)));
146 // The value added in hook_field_load() is found.
147 $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)));
148 }
149 }
150 }
151
152 /**
153 * Test the 'multiple' load feature.
154 */
155 function testFieldAttachLoadMultiple() {
156 $entity_type = 'test_entity';
157 $langcode = FIELD_LANGUAGE_NONE;
158
159 // Define 2 bundles.
160 $bundles = array(
161 1 => 'test_bundle_1',
162 2 => 'test_bundle_2',
163 );
164 field_test_create_bundle($bundles[1]);
165 field_test_create_bundle($bundles[2]);
166 // Define 3 fields:
167 // - field_1 is in bundle_1 and bundle_2,
168 // - field_2 is in bundle_1,
169 // - field_3 is in bundle_2.
170 $field_bundles_map = array(
171 1 => array(1, 2),
172 2 => array(1),
173 3 => array(2),
174 );
175 for ($i = 1; $i <= 3; $i++) {
176 $field_names[$i] = 'field_' . $i;
177 $field = array('field_name' => $field_names[$i], 'type' => 'test_field');
178 $field = field_create_field($field);
179 $field_ids[$i] = $field['id'];
180 foreach ($field_bundles_map[$i] as $bundle) {
181 $instance = array(
182 'field_name' => $field_names[$i],
183 'object_type' => 'test_entity',
184 'bundle' => $bundles[$bundle],
185 'settings' => array(
186 // Configure the instance so that we test hook_field_load()
187 // (see field_test_field_load() in field_test.module).
188 'test_hook_field_load' => TRUE,
189 ),
190 );
191 field_create_instance($instance);
192 }
193 }
194
195 // Create one test entity per bundle, with random values.
196 foreach ($bundles as $index => $bundle) {
197 $entities[$index] = field_test_create_stub_entity($index, $index, $bundle);
198 $entity = clone($entities[$index]);
199 $instances = field_info_instances('test_entity', $bundle);
200 foreach ($instances as $field_name => $instance) {
201 $values[$index][$field_name] = mt_rand(1, 127);
202 $entity->$field_name = array($langcode => array(array('value' => $values[$index][$field_name])));
203 }
204 field_attach_insert($entity_type, $entity);
205 }
206
207 // Check that a single load correctly loads field values for both entities.
208 field_attach_load($entity_type, $entities);
209 foreach ($entities as $index => $entity) {
210 $instances = field_info_instances($entity_type, $bundles[$index]);
211 foreach ($instances as $field_name => $instance) {
212 // The field value loaded matches the one inserted.
213 $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], $values[$index][$field_name], t('Entity %index: expected value was found.', array('%index' => $index)));
214 // The value added in hook_field_load() is found.
215 $this->assertEqual($entity->{$field_name}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => $index)));
216 }
217 }
218
219 // Check that the single-field load option works.
220 $entity = field_test_create_stub_entity(1, 1, $bundles[1]);
221 field_attach_load($entity_type, array(1 => $entity), FIELD_LOAD_CURRENT, array('field_id' => $field_ids[1]));
222 $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['value'], $values[1][$field_names[1]], t('Entity %index: expected value was found.', array('%index' => 1)));
223 $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => 1)));
224 $this->assert(!isset($entity->{$field_names[2]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 2, '%field_name' => $field_names[2])));
225 $this->assert(!isset($entity->{$field_names[3]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 3, '%field_name' => $field_names[3])));
226 }
227
228 /**
229 * Test saving and loading fields using different storage backends.
230 */
231 function testFieldAttachSaveLoadDifferentStorage() {
232 $entity_type = 'test_entity';
233 $langcode = FIELD_LANGUAGE_NONE;
234
235 // Create two fields using different storage backends, and their instances.
236 $fields = array(
237 array(
238 'field_name' => 'field_1',
239 'type' => 'test_field',
240 'cardinality' => 4,
241 'storage' => array('type' => 'field_sql_storage')
242 ),
243 array(
244 'field_name' => 'field_2',
245 'type' => 'test_field',
246 'cardinality' => 4,
247 'storage' => array('type' => 'field_test_storage')
248 ),
249 );
250 foreach ($fields as $field) {
251 field_create_field($field);
252 $instance = array(
253 'field_name' => $field['field_name'],
254 'object_type' => 'test_entity',
255 'bundle' => 'test_bundle',
256 );
257 field_create_instance($instance);
258 }
259
260 $entity_init = field_test_create_stub_entity();
261
262 // Create entity and insert random values.
263 $entity = clone($entity_init);
264 $values = array();
265 foreach ($fields as $field) {
266 $values[$field['field_name']] = $this->_generateTestFieldValues($this->field['cardinality']);
267 $entity->{$field['field_name']}[$langcode] = $values[$field['field_name']];
268 }
269 field_attach_insert($entity_type, $entity);
270
271 // Check that values are loaded as expected.
272 $entity = clone($entity_init);
273 field_attach_load($entity_type, array($entity->ftid => $entity));
274 foreach ($fields as $field) {
275 $this->assertEqual($values[$field['field_name']], $entity->{$field['field_name']}[$langcode], t('%storage storage: expected values were found.', array('%storage' => $field['storage']['type'])));
276 }
277 }
278
279 /**
280 * Test storage details alteration.
281 *
282 * @see field_test_storage_details_alter()
283 */
284 function testFieldStorageDetailsAlter() {
285 $field_name = 'field_test_change_my_details';
286 $field = array(
287 'field_name' => $field_name,
288 'type' => 'test_field',
289 'cardinality' => 4,
290 'storage' => array('type' => 'field_test_storage'),
291 );
292 $field = field_create_field($field);
293 $instance = array(
294 'field_name' => $field_name,
295 'object_type' => 'test_entity',
296 'bundle' => 'test_bundle',
297 );
298 field_create_instance($instance);
299
300 $field = field_info_field($instance['field_name']);
301 $instance = field_info_instance($instance['object_type'], $instance['field_name'], $instance['bundle']);
302
303 // The storage details are indexed by a storage engine type.
304 $this->assertTrue(array_key_exists('drupal_variables', $instance['storage_details']), t('The storage type is Drupal variables.'));
305
306 $details = $instance['storage_details']['drupal_variables'];
307
308 // The field_test storage details are indexed by variable name. The details
309 // are altered, so moon and mars are correct for this test.
310 $this->assertTrue(array_key_exists('moon', $details[FIELD_LOAD_CURRENT]), t('Moon is available in the instance array.'));
311 $this->assertTrue(array_key_exists('mars', $details[FIELD_LOAD_REVISION]), t('Mars is available in the instance array.'));
312
313 // Test current and revision storage details together because the columns
314 // are the same.
315 foreach ((array) $field['columns'] as $column_name => $attributes) {
316 $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]')));
317 $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]')));
318 }
319 }
320
321 /**
322 * Tests insert and update with missing or NULL fields.
323 */
324 function testFieldAttachSaveMissingData() {
325 $entity_type = 'test_entity';
326 $entity_init = field_test_create_stub_entity();
327 $langcode = FIELD_LANGUAGE_NONE;
328
329 // Insert: Field is missing.
330 $entity = clone($entity_init);
331 field_attach_insert($entity_type, $entity);
332
333 $entity = clone($entity_init);
334 field_attach_load($entity_type, array($entity->ftid => $entity));
335 $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: missing field results in no value saved'));
336
337 // Insert: Field is NULL.
338 field_cache_clear();
339 $entity = clone($entity_init);
340 $entity->{$this->field_name} = NULL;
341 field_attach_insert($entity_type, $entity);
342
343 $entity = clone($entity_init);
344 field_attach_load($entity_type, array($entity->ftid => $entity));
345 $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: NULL field results in no value saved'));
346
347 // Add some real data.
348 field_cache_clear();
349 $entity = clone($entity_init);
350 $values = $this->_generateTestFieldValues(1);
351 $entity->{$this->field_name}[$langcode] = $values;
352 field_attach_insert($entity_type, $entity);
353
354 $entity = clone($entity_init);
355 field_attach_load($entity_type, array($entity->ftid => $entity));
356 $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved'));
357
358 // Update: Field is missing. Data should survive.
359 field_cache_clear();
360 $entity = clone($entity_init);
361 field_attach_update($entity_type, $entity);
362
363 $entity = clone($entity_init);
364 field_attach_load($entity_type, array($entity->ftid => $entity));
365 $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Update: missing field leaves existing values in place'));
366
367 // Update: Field is NULL. Data should be wiped.
368 field_cache_clear();
369 $entity = clone($entity_init);
370 $entity->{$this->field_name} = NULL;
371 field_attach_update($entity_type, $entity);
372
373 $entity = clone($entity_init);
374 field_attach_load($entity_type, array($entity->ftid => $entity));
375 $this->assertTrue(empty($entity->{$this->field_name}), t('Update: NULL field removes existing values'));
376
377 // Re-add some data.
378 field_cache_clear();
379 $entity = clone($entity_init);
380 $values = $this->_generateTestFieldValues(1);
381 $entity->{$this->field_name}[$langcode] = $values;
382 field_attach_update($entity_type, $entity);
383
384 $entity = clone($entity_init);
385 field_attach_load($entity_type, array($entity->ftid => $entity));
386 $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved'));
387
388 // Update: Field is empty array. Data should be wiped.
389 field_cache_clear();
390 $entity = clone($entity_init);
391 $entity->{$this->field_name} = array();
392 field_attach_update($entity_type, $entity);
393
394 $entity = clone($entity_init);
395 field_attach_load($entity_type, array($entity->ftid => $entity));
396 $this->assertTrue(empty($entity->{$this->field_name}), t('Update: empty array removes existing values'));
397 }
398
399 /**
400 * Test insert with missing or NULL fields, with default value.
401 */
402 function testFieldAttachSaveMissingDataDefaultValue() {
403 // Add a default value.
404 $this->instance['default_value_function'] = 'field_test_default_value';
405 field_update_instance($this->instance);
406
407 $entity_type = 'test_entity';
408 $entity_init = field_test_create_stub_entity();
409 $langcode = FIELD_LANGUAGE_NONE;
410
411 // Insert: Field is NULL.
412 $entity = clone($entity_init);
413 $entity->{$this->field_name}[$langcode] = NULL;
414 field_attach_insert($entity_type, $entity);
415
416 $entity = clone($entity_init);
417 field_attach_load($entity_type, array($entity->ftid => $entity));
418 $this->assertTrue(empty($entity->{$this->field_name}[$langcode]), t('Insert: NULL field results in no value saved'));
419
420 // Insert: Field is missing.
421 field_cache_clear();
422 $entity = clone($entity_init);
423 field_attach_insert($entity_type, $entity);
424
425 $entity = clone($entity_init);
426 field_attach_load($entity_type, array($entity->ftid => $entity));
427 $values = field_test_default_value($entity_type, $entity, $this->field, $this->instance);
428 $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Insert: missing field results in default value saved'));
429 }
430
431 /**
432 * Test field_attach_delete().
433 */
434 function testFieldAttachDelete() {
435 $entity_type = 'test_entity';
436 $langcode = FIELD_LANGUAGE_NONE;
437 $rev[0] = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
438
439 // Create revision 0
440 $values = $this->_generateTestFieldValues($this->field['cardinality']);
441 $rev[0]->{$this->field_name}[$langcode] = $values;
442 field_attach_insert($entity_type, $rev[0]);
443
444 // Create revision 1
445 $rev[1] = field_test_create_stub_entity(0, 1, $this->instance['bundle']);
446 $rev[1]->{$this->field_name}[$langcode] = $values;
447 field_attach_update($entity_type, $rev[1]);
448
449 // Create revision 2
450 $rev[2] = field_test_create_stub_entity(0, 2, $this->instance['bundle']);
451 $rev[2]->{$this->field_name}[$langcode] = $values;
452 field_attach_update($entity_type, $rev[2]);
453
454 // Confirm each revision loads
455 foreach (array_keys($rev) as $vid) {
456 $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']);
457 field_attach_load_revision($entity_type, array(0 => $read));
458 $this->assertEqual(count($read->{$this->field_name}[$langcode]), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values.");
459 }
460
461 // Delete revision 1, confirm the other two still load.
462 field_attach_delete_revision($entity_type, $rev[1]);
463 foreach (array(0, 2) as $vid) {
464 $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']);
465 field_attach_load_revision($entity_type, array(0 => $read));
466 $this->assertEqual(count($read->{$this->field_name}[$langcode]), $this->field['cardinality'], "The test object revision $vid has {$this->field['cardinality']} values.");
467 }
468
469 // Confirm the current revision still loads
470 $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']);
471 field_attach_load($entity_type, array(0 => $read));
472 $this->assertEqual(count($read->{$this->field_name}[$langcode]), $this->field['cardinality'], "The test object current revision has {$this->field['cardinality']} values.");
473
474 // Delete all field data, confirm nothing loads
475 field_attach_delete($entity_type, $rev[2]);
476 foreach (array(0, 1, 2) as $vid) {
477 $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']);
478 field_attach_load_revision($entity_type, array(0 => $read));
479 $this->assertIdentical($read->{$this->field_name}, array(), "The test object revision $vid is deleted.");
480 }
481 $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']);
482 field_attach_load($entity_type, array(0 => $read));
483 $this->assertIdentical($read->{$this->field_name}, array(), t('The test object current revision is deleted.'));
484 }
485
486 /**
487 * Test field_attach_create_bundle() and field_attach_rename_bundle().
488 */
489 function testFieldAttachCreateRenameBundle() {
490 // Create a new bundle. This has to be initiated by the module so that its
491 // hook_entity_info() is consistent.
492 $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName());
493 field_test_create_bundle($new_bundle);
494
495 // Add an instance to that bundle.
496 $this->instance['bundle'] = $new_bundle;
497 field_create_instance($this->instance);
498
499 // Save an object with data in the field.
500 $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
501 $langcode = FIELD_LANGUAGE_NONE;
502 $values = $this->_generateTestFieldValues($this->field['cardinality']);
503 $entity->{$this->field_name}[$langcode] = $values;
504 $entity_type = 'test_entity';
505 field_attach_insert($entity_type, $entity);
506
507 // Verify the field data is present on load.
508 $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
509 field_attach_load($entity_type, array(0 => $entity));
510 $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], "Data is retrieved for the new bundle");
511
512 // Rename the bundle. This has to be initiated by the module so that its
513 // hook_entity_info() is consistent.
514 $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName());
515 field_test_rename_bundle($this->instance['bundle'], $new_bundle);
516
517 // Check that the instance definition has been updated.
518 $this->instance = field_info_instance($entity_type, $this->field_name, $new_bundle);
519 $this->assertIdentical($this->instance['bundle'], $new_bundle, "Bundle name has been updated in the instance.");
520
521 // Verify the field data is present on load.
522 $entity = field_test_create_stub_entity(0, 0, $new_bundle);
523 field_attach_load($entity_type, array(0 => $entity));
524 $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], "Bundle name has been updated in the field storage");
525 }
526
527 /**
528 * Test field_attach_delete_bundle().
529 */
530 function testFieldAttachDeleteBundle() {
531 // Create a new bundle. This has to be initiated by the module so that its
532 // hook_entity_info() is consistent.
533 $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName());
534 field_test_create_bundle($new_bundle);
535
536 // Add an instance to that bundle.
537 $this->instance['bundle'] = $new_bundle;
538 field_create_instance($this->instance);
539
540 // Create a second field for the test bundle
541 $field_name = drupal_strtolower($this->randomName() . '_field_name');
542 $field = array('field_name' => $field_name, 'type' => 'test_field', 'cardinality' => 1);
543 field_create_field($field);
544 $instance = array(
545 'field_name' => $field_name,
546 'object_type' => 'test_entity',
547 'bundle' => $this->instance['bundle'],
548 'label' => $this->randomName() . '_label',
549 'description' => $this->randomName() . '_description',
550 'weight' => mt_rand(0, 127),
551 // test_field has no instance settings
552 'widget' => array(
553 'type' => 'test_field_widget',
554 'settings' => array(
555 'size' => mt_rand(0, 255))));
556 field_create_instance($instance);
557
558 // Save an object with data for both fields
559 $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
560 $langcode = FIELD_LANGUAGE_NONE;
561 $values = $this->_generateTestFieldValues($this->field['cardinality']);
562 $entity->{$this->field_name}[$langcode] = $values;
563 $entity->{$field_name}[$langcode] = $this->_generateTestFieldValues(1);
564 field_attach_insert('test_entity', $entity);
565
566 // Verify the fields are present on load
567 $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
568 field_attach_load('test_entity', array(0 => $entity));
569 $this->assertEqual(count($entity->{$this->field_name}[$langcode]), 4, 'First field got loaded');
570 $this->assertEqual(count($entity->{$field_name}[$langcode]), 1, 'Second field got loaded');
571
572 // Delete the bundle. This has to be initiated by the module so that its
573 // hook_entity_info() is consistent.
574 field_test_delete_bundle($this->instance['bundle']);
575
576 // Verify no data gets loaded
577 $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
578 field_attach_load('test_entity', array(0 => $entity));
579 $this->assertFalse(isset($entity->{$this->field_name}[$langcode]), 'No data for first field');
580 $this->assertFalse(isset($entity->{$field_name}[$langcode]), 'No data for second field');
581
582 // Verify that the instances are gone
583 $this->assertFalse(field_read_instance('test_entity', $this->field_name, $this->instance['bundle']), "First field is deleted");
584 $this->assertFalse(field_read_instance('test_entity', $field_name, $instance['bundle']), "Second field is deleted");
585 }
586
587 /**
588 * Test field_attach_query().
589 */
590 function testFieldAttachQuery() {
591 $cardinality = $this->field['cardinality'];
592 $langcode = FIELD_LANGUAGE_NONE;
593
594 // Create an additional bundle with an instance of the field.
595 field_test_create_bundle('test_bundle_1', 'Test Bundle 1');
596 $this->instance2 = $this->instance;
597 $this->instance2['bundle'] = 'test_bundle_1';
598 field_create_instance($this->instance2);
599
600 // Create instances of both fields on the second entity type.
601 $instance = $this->instance;
602 $instance['object_type'] = 'test_cacheable_entity';
603 field_create_instance($instance);
604 $instance2 = $this->instance2;
605 $instance2['object_type'] = 'test_cacheable_entity';
606 field_create_instance($instance2);
607
608 // Unconditional count query returns 0.
609 $count = field_attach_query($this->field_id, array(), array('count' => TRUE));
610 $this->assertEqual($count, 0, t('With no objects, count query returns 0.'));
611
612 // Create two test objects, using two different types and bundles.
613 $entity_types = array(1 => 'test_entity', 2 => 'test_cacheable_entity');
614 $entities = array(1 => field_test_create_stub_entity(1, 1, 'test_bundle'), 2 => field_test_create_stub_entity(2, 2, 'test_bundle_1'));
615
616 // Create first test object with random (distinct) values.
617 $values = array();
618 for ($delta = 0; $delta < $cardinality; $delta++) {
619 do {
620 $value = mt_rand(1, 127);
621 } while (in_array($value, $values));
622 $values[$delta] = $value;
623 $entities[1]->{$this->field_name}[$langcode][$delta] = array('value' => $values[$delta]);
624 }
625 field_attach_insert($entity_types[1], $entities[1]);
626
627 // Unconditional count query returns 1.
628 $count = field_attach_query($this->field_id, array(), array('count' => TRUE));
629 $this->assertEqual($count, 1, t('With one object, count query returns @count.', array('@count' => $count)));
630
631 // Create second test object, sharing a value with the first one.
632 $common_value = $values[$cardinality - 1];
633 $entities[2]->{$this->field_name} = array($langcode => array(array('value' => $common_value)));
634 field_attach_insert($entity_types[2], $entities[2]);
635
636 // Query on the object's values.
637 for ($delta = 0; $delta < $cardinality; $delta++) {
638 $conditions = array(array('value', $values[$delta]));
639 $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
640 $this->assertTrue(isset($result[$entity_types[1]][1]), t('Query on value %delta returns the object', array('%delta' => $delta)));
641
642 $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE));
643 $this->assertEqual($count, ($values[$delta] == $common_value) ? 2 : 1, t('Count query on value %delta counts %count objects', array('%delta' => $delta, '%count' => $count)));
644 }
645
646 // Query on a value that is not in the object.
647 do {
648 $different_value = mt_rand(1, 127);
649 } while (in_array($different_value, $values));
650 $conditions = array(array('value', $different_value));
651 $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
652 $this->assertFalse(isset($result[$entity_types[1]][1]), t("Query on a value that is not in the object doesn't return the object"));
653 $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE));
654 $this->assertEqual($count, 0, t("Count query on a value that is not in the object doesn't count the object"));
655
656 // Query on the value shared by both objects, and discriminate using
657 // additional conditions.
658
659 $conditions = array(array('value', $common_value));
660 $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
661 $this->assertTrue(isset($result[$entity_types[1]][1]) && isset($result[$entity_types[2]][2]), t('Query on a value common to both objects returns both objects'));
662 $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE));
663 $this->assertEqual($count, 2, t('Count query on a value common to both objects counts both objects'));
664
665 $conditions = array(array('type', $entity_types[1]), array('value', $common_value));
666 $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
667 $this->assertTrue(isset($result[$entity_types[1]][1]) && !isset($result[$entity_types[2]][2]), t("Query on a value common to both objects and a 'type' condition only returns the relevant object"));
668 $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE));
669 $this->assertEqual($count, 1, t("Count query on a value common to both objects and a 'type' condition only returns the relevant object"));
670
671 $conditions = array(array('bundle', $entities[1]->fttype), array('value', $common_value));
672 $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
673 $this->assertTrue(isset($result[$entity_types[1]][1]) && !isset($result[$entity_types[2]][2]), t("Query on a value common to both objects and a 'bundle' condition only returns the relevant object"));
674 $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE));
675 $this->assertEqual($count, 1, t("Count query on a value common to both objects and a 'bundle' condition only counts the relevant object"));
676
677 $conditions = array(array('entity_id', $entities[1]->ftid), array('value', $common_value));
678 $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
679 $this->assertTrue(isset($result[$entity_types[1]][1]) && !isset($result[$entity_types[2]][2]), t("Query on a value common to both objects and an 'entity_id' condition only returns the relevant object"));
680 $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE));
681 $this->assertEqual($count, 1, t("Count query on a value common to both objects and an 'entity_id' condition only counts the relevant object"));
682
683 // Test result format.
684 $conditions = array(array('value', $values[0]));
685 $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
686 $expected = array(
687 $entity_types[1] => array(
688 $entities[1]->ftid => field_test_create_stub_entity($entities[1]->ftid, $entities[1]->ftvid),
689 )
690 );
691 $this->assertEqual($result, $expected, t('Result format is correct.'));
692
693 // Now test the count/offset paging capability.
694
695 // Create a new bundle with an instance of the field.
696 field_test_create_bundle('offset_bundle', 'Offset Test Bundle');
697 $this->instance2 = $this->instance;
698 $this->instance2['bundle'] = 'offset_bundle';
699 field_create_instance($this->instance2);
700
701 // Create 20 test objects, using the new bundle, but with
702 // non-sequential ids so we can tell we are getting the right ones
703 // back. We do not need unique values since field_attach_query()
704 // won't return them anyway.
705 $offset_entities = array();
706 $offset_id = mt_rand(1, 3);
707 for ($i = 0; $i < 20; ++$i) {
708 $offset_id += mt_rand(2, 5);
709 $offset_entities[$offset_id] = field_test_create_stub_entity($offset_id, $offset_id, 'offset_bundle');
710 $offset_entities[$offset_id]->{$this->field_name}[$langcode][0] = array('value' => $offset_id);
711 field_attach_insert('test_entity', $offset_entities[$offset_id]);
712 }
713
714 // Query for the offset entities in batches, making sure we get
715 // back the right ones.
716 $cursor = 0;
717 foreach (array(1 => 1, 3 => 3, 5 => 5, 8 => 8, 13 => 3) as $count => $expect) {
718 $found = field_attach_query($this->field_id, array(array('bundle', 'offset_bundle')), array('limit' => $count, 'cursor' => &$cursor));
719 if (isset($found['test_entity'])) {
720 $this->assertEqual(count($found['test_entity']), $expect, t('Requested @count, expected @expect, got @found, cursor @cursor', array('@count' => $count, '@expect' => $expect, '@found' => count($found['test_entity']), '@cursor' => $cursor)));
721 foreach ($found['test_entity'] as $id => $entity) {
722 $this->assert(isset($offset_entities[$id]), "Entity $id found");
723 unset($offset_entities[$id]);
724 }
725 }
726 else {
727 $this->assertEqual(0, $expect, t('Requested @count, expected @expect, got @found, cursor @cursor', array('@count' => $count, '@expect' => $expect, '@found' => 0, '@cursor' => $cursor)));
728 }
729 }
730 $this->assertEqual(count($offset_entities), 0, "All entities found");
731 $this->assertEqual($cursor, FIELD_QUERY_COMPLETE, "Cursor is FIELD_QUERY_COMPLETE");
732 }
733
734 /**
735 * Test field_attach_query_revisions().
736 */
737 function testFieldAttachQueryRevisions() {
738 $cardinality = $this->field['cardinality'];
739
740 // Create first object revision with random (distinct) values.
741 $entity_type = 'test_entity';
742 $entities = array(1 => field_test_create_stub_entity(1, 1), 2 => field_test_create_stub_entity(1, 2));
743 $langcode = FIELD_LANGUAGE_NONE;
744 $values = array();
745 for ($delta = 0; $delta < $cardinality; $delta++) {
746 do {
747 $value = mt_rand(1, 127);
748 } while (in_array($value, $values));
749 $values[$delta] = $value;
750 $entities[1]->{$this->field_name}[$langcode][$delta] = array('value' => $values[$delta]);
751 }
752 field_attach_insert($entity_type, $entities[1]);
753
754 // Create second object revision, sharing a value with the first one.
755 $common_value = $values[$cardinality - 1];
756 $entities[2]->{$this->field_name}[$langcode][0] = array('value' => $common_value);
757 field_attach_update($entity_type, $entities[2]);
758
759 // Query on the object's values.
760 for ($delta = 0; $delta < $cardinality; $delta++) {
761 $conditions = array(array('value', $values[$delta]));
762 $result = field_attach_query_revisions($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
763 $this->assertTrue(isset($result[$entity_type][1]), t('Query on value %delta returns the object', array('%delta' => $delta)));
764 }
765
766 // Query on a value that is not in the object.
767 do {
768 $different_value = mt_rand(1, 127);
769 } while (in_array($different_value, $values));
770 $conditions = array(array('value', $different_value));
771 $result = field_attach_query_revisions($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
772 $this->assertFalse(isset($result[$entity_type][1]), t("Query on a value that is not in the object doesn't return the object"));
773
774 // Query on the value shared by both objects, and discriminate using
775 // additional conditions.
776
777 $conditions = array(array('value', $common_value));
778 $result = field_attach_query_revisions($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
779 $this->assertTrue(isset($result[$entity_type][1]) && isset($result[$entity_type][2]), t('Query on a value common to both objects returns both objects'));
780
781 $conditions = array(array('revision_id', $entities[1]->ftvid), array('value', $common_value));
782 $result = field_attach_query_revisions($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
783 $this->assertTrue(isset($result[$entity_type][1]) && !isset($result[$entity_type][2]), t("Query on a value common to both objects and a 'revision_id' condition only returns the relevant object"));
784
785 // Test FIELD_QUERY_RETURN_IDS result format.
786 $conditions = array(array('value', $values[0]));
787 $result = field_attach_query_revisions($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
788 $expected = array(
789 $entity_type => array(
790 $entities[1]->ftid => field_test_create_stub_entity($entities[1]->ftid, $entities[1]->ftvid),
791 )
792 );
793 $this->assertEqual($result, $expected, t('FIELD_QUERY_RETURN_IDS result format returns the expect result'));
794 }
795 }
796
797 /**
798 * Unit test class for non-storage related field_attach_* functions.
799 */
800 class FieldAttachOtherTestCase extends FieldAttachTestCase {
801 public static function getInfo() {
802 return array(
803 'name' => 'Field attach tests (other)',
804 'description' => 'Test other Field Attach API functions.',
805 'group' => 'Field',
806 );
807 }
808
809 /**
810 * Test field_attach_view() and field_atach_prepare_view().
811 */
812 function testFieldAttachView() {
813 $entity_type = 'test_entity';
814 $entity_init = field_test_create_stub_entity();
815 $langcode = FIELD_LANGUAGE_NONE;
816
817 // Populate values to be displayed.
818 $values = $this->_generateTestFieldValues($this->field['cardinality']);
819 $entity_init->{$this->field_name}[$langcode] = $values;
820
821 // Simple formatter, label displayed.
822 $entity = clone($entity_init);
823 $formatter_setting = $this->randomName();
824 $this->instance['display'] = array(
825 'full' => array(
826 'label' => 'above',
827 'type' => 'field_test_default',
828 'settings' => array(
829 'test_formatter_setting' => $formatter_setting,
830 )
831 ),
832 );
833 field_update_instance($this->instance);
834 field_attach_prepare_view($entity_type, array($entity->ftid => $entity));
835 $entity->content = field_attach_view($entity_type, $entity);
836 $output = drupal_render($entity->content);
837 $this->content = $output;
838 $this->assertRaw($this->instance['label'], "Label is displayed.");
839 foreach ($values as $delta => $value) {
840 $this->content = $output;
841 $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
842 }
843
844 // Label hidden.
845 $entity = clone($entity_init);
846 $this->instance['display']['full']['label'] = 'hidden';
847 field_update_instance($this->instance);
848 field_attach_prepare_view($entity_type, array($entity->ftid => $entity));
849 $entity->content = field_attach_view($entity_type, $entity);
850 $output = drupal_render($entity->content);
851 $this->content = $output;
852 $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed.");
853
854 // Field hidden.
855 $entity = clone($entity_init);
856 $this->instance['display'] = array(
857 'full' => array(
858 'label' => 'above',
859 'type' => 'hidden',
860 ),
861 );
862 field_update_instance($this->instance);
863 field_attach_prepare_view($entity_type, array($entity->ftid => $entity));
864 $entity->content = field_attach_view($entity_type, $entity);
865 $output = drupal_render($entity->content);
866 $this->content = $output;
867 $this->assertNoRaw($this->instance['label'], "Hidden field: label is not displayed.");
868 foreach ($values as $delta => $value) {
869 $this->assertNoRaw($value['value'], "Hidden field: value $delta is not displayed.");
870 }
871
872 // Multiple formatter.
873 $entity = clone($entity_init);
874 $formatter_setting = $this->randomName();
875 $this->instance['display'] = array(
876 'full' => array(
877 'label' => 'above',
878 'type' => 'field_test_multiple',
879 'settings' => array(
880 'test_formatter_setting_multiple' => $formatter_setting,
881 )
882 ),
883 );
884 field_update_instance($this->instance);
885 field_attach_prepare_view($entity_type, array($entity->ftid => $entity));
886 $entity->content = field_attach_view($entity_type, $entity);
887 $output = drupal_render($entity->content);
888 $display = $formatter_setting;
889 foreach ($values as $delta => $value) {
890 $display .= "|$delta:{$value['value']}";
891 }
892 $this->content = $output;
893 $this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied.");
894
895 // Test a formatter that uses hook_field_formatter_prepare_view()..
896 $entity = clone($entity_init);
897 $formatter_setting = $this->randomName();
898 $this->instance['display'] = array(
899 'full' => array(
900 'label' => 'above',
901 'type' => 'field_test_needs_additional_data',
902 'settings' => array(
903 'test_formatter_setting_additional' => $formatter_setting,
904 )
905 ),
906 );
907 field_update_instance($this->instance);
908 field_attach_prepare_view($entity_type, array($entity->ftid => $entity));
909 $entity->content = field_attach_view($entity_type, $entity);
910 $output = drupal_render($entity->content);
911 $this->content = $output;
912 foreach ($values as $delta => $value) {
913 $this->content = $output;
914 $expected = $formatter_setting . '|' . $value['value'] . '|' . ($value['value'] + 1);
915 $this->assertRaw($expected, "Value $delta is displayed, formatter settings are applied.");
916 }
917
918 // TODO:
919 // - check display order with several fields
920
921 // Preprocess template.
922 $variables = array();
923 field_attach_preprocess($entity_type, $entity, $entity->content, $variables);
924 $result = TRUE;
925 foreach ($values as $delta => $item) {
926 if ($variables[$this->field_name][$delta]['value'] !== $item['value']) {
927 $result = FALSE;
928 break;
929 }
930 }
931 $this->assertTrue($result, t('Variable $@field_name correctly populated.', array('@field_name' => $this->field_name)));
932 }
933
934 /**
935 * Test field cache.
936 */
937 function testFieldAttachCache() {
938 // Initialize random values and a test entity.
939 $entity_init = field_test_create_stub_entity(1, 1, $this->instance['bundle']);
940 $langcode = FIELD_LANGUAGE_NONE;
941 $values = $this->_generateTestFieldValues($this->field['cardinality']);
942
943 // Non-cacheable entity type.
944 $entity_type = 'test_entity';
945 $cid = "field:$entity_type:{$entity_init->ftid}";
946
947 // Check that no initial cache entry is present.
948 $this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no initial cache entry'));
949
950 // Save, and check that no cache entry is present.
951 $entity = clone($entity_init);
952 $entity->{$this->field_name}[$langcode] = $values;
953 field_attach_insert($entity_type, $entity);
954 $this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no cache entry on insert'));
955
956 // Load, and check that no cache entry is present.
957 $entity = clone($entity_init);
958 field_attach_load($entity_type, array($entity->ftid => $entity));
959 $this->assertFalse(cache_g