2
// $Id: content.crud.test,v 1.4.2.16 2008/12/08 12:41:08 yched Exp $
5
// - Test search indexing
6
// - Test values reordering with preview and failed validation
9
* Base class for CCK CRUD tests.
10
* Defines many helper functions useful for writing CCK CRUD tests.
12
class ContentCrudTestCase extends DrupalWebTestCase {
13
var $enabled_schema = FALSE;
14
var $content_types = array();
16
var $last_field = NULL;
17
var $next_field_n = 1;
20
* Enable CCK, Text, and Schema modules.
23
$args = func_get_args();
24
$modules = array_merge(array('content', 'schema', 'text'), $args);
25
call_user_func_array(array('parent','setUp'), $modules);
26
module_load_include('inc', 'content', 'includes/content.crud');
29
// Database schema related helper functions
32
* Checks that the database itself and the reported database schema match the
33
* expected columns for the given tables.
34
* @param $tables An array containing the key 'per_field' and/or the key 'per_type'.
35
* These keys should have array values with table names as the keys (without the 'content_' / 'content_type_' prefix)
36
* These keys should have either NULL value to indicate the table should be absent, or
37
* array values containing column names. The column names can themselves be arrays, in
38
* which case the contents of the array are treated as column names and prefixed with
41
* For example, if called with the following as an argument:
43
* 'per_field' => array(
44
* 'st_f1' => array('delta', 'field_f1' => array('value, 'format')),
45
* 'st_f2' => NULL, // no content_field_f2 table
47
* 'per_type' => array(
48
* 'st_t1' => array('field_f2' => array('value'), 'field_f3' => array('value', 'format')),
49
* 'st_t2' => array(), // only 'nid' and 'vid' columns
50
* 'st_t3' => NULL, // no content_type_t3 table
53
* Then the database and schema will be checked to ensure that:
54
* content_st_f1 table contains fields nid, vid, delta, field_f1_value, field_f1_format
55
* content_st_f2 table is absent
56
* content_type_st_t1 table contains fields nid, vid, field_f2_value, field_f3_value, field_f3_format
57
* content_type_st_t2 table contains fields nid, vid
58
* content_type_st_t3 table is absent
60
function assertSchemaMatchesTables($tables) {
61
$groups = array('per_field' => 'content_', 'per_type' => 'content_type_');
63
foreach ($groups as $group => $table_prefix) {
64
if (isset($tables[$group])) {
65
foreach ($tables[$group] as $entity => $columns) {
66
if (isset($columns)) {
67
$db_columns = array('nid', 'vid');
68
foreach ($columns as $prefix => $items) {
69
if (is_array($items)) {
70
foreach ($items as $item) {
71
$db_columns[] = $prefix .'_'. $item;
75
$db_columns[] = $items;
78
$this->_assertSchemaMatches($table_prefix . $entity, $db_columns);
81
$this->_assertTableNotExists($table_prefix . $entity);
89
* Helper function for assertSchemaMatchesTables
90
* Checks that the given database table does NOT exist
91
* @param $table Name of the table to check
93
function _assertTableNotExists($table) {
94
$this->assertFalse(db_table_exists($table), t('Table !table is absent', array('!table' => $table)));
98
* Helper function for assertSchemaMatchesTables
99
* Checks that the database and schema for the given table contain only the expected fields.
100
* @param $table Name of the table to check
101
* @param $columns Array of column names
103
function _assertSchemaMatches($table, $columns) {
104
// First test: check the expected structure matches the stored schema.
105
$schema = drupal_get_schema($table, TRUE);
106
$mismatches = array();
107
if ($schema === FALSE) {
108
$mismatches[] = t('table does not exist');
111
$fields = $schema['fields'];
112
foreach ($columns as $field) {
113
if (!isset($fields[$field])) {
114
$mismatches[] = t('field !field is missing from table', array('!field' => $field));
117
$columns_reverse = array_flip($columns);
118
foreach ($fields as $name => $info) {
119
if(!isset($columns_reverse[$name])) {
120
$mismatches[] = t('table contains unexpected field !field', array('!field' => $name));
124
$this->assertEqual(count($mismatches), 0, t('Table !table matches schema: !details',
125
array('!table' => $table, '!details' => implode($mismatches, ', '))));
127
// Second test: check the schema matches the actual db structure.
128
// This is the part that relies on schema.module.
129
if (!$this->enabled_schema) {
130
$this->enabled_schema = module_exists('schema');
132
if ($this->enabled_schema) {
133
// Clunky workaround for http://drupal.org/node/215198
134
$prefixed_table = db_prefix_tables('{'. $table .'}');
135
$inspect = schema_invoke('inspect', $prefixed_table);
136
$inspect = isset($inspect[$table]) ? $inspect[$table] : NULL;
137
$compare = schema_compare_table($schema, $inspect);
138
if ($compare['status'] == 'missing') {
139
$compare['reasons'] = array(t('table does not exist'));
143
$compare = array('status' => 'unknown', 'reasons' => array(t('cannot enable schema module')));
145
$this->assertEqual($compare['status'], 'same', t('Table schema for !table matches database: !details',
146
array('!table' => $table, '!details' => implode($compare['reasons'], ', '))));
149
// Node data helper functions
152
* Helper function for assertNodeSaveValues. Recursively checks that
153
* all the keys of a table are present in a second and have the same value.
155
function _compareArrayForChanges($fields, $data, $message, $prefix = '') {
156
foreach ($fields as $key => $value) {
157
$newprefix = ($prefix == '') ? $key : $prefix .']['. $key;
158
if (is_array($value)) {
159
$compare_to = isset($data[$key]) ? $data[$key] : array();
160
$this->_compareArrayForChanges($value, $compare_to, $message, $newprefix);
163
$this->assertEqual($value, $data[$key], t($message, array('!key' => $newprefix)));
169
* Checks that after a node is saved using node_save, the values to be saved
170
* match up with the output from node_load.
171
* @param $node Either a node object, or the index of an acquired node
172
* @param $values Array of values to be merged with the node and passed to node_save
173
* @return The values array
175
function assertNodeSaveValues($node, $values) {
176
if (is_numeric($node) && isset($this->nodes[$node])) {
177
$node = $this->nodes[$node];
179
$node = $values + (array)$node;
180
$node = (object)$node;
182
$this->assertNodeValues($node, $values);
187
* Checks that the output from node_load matches the expected values.
188
* @param $node Either a node object, or the index of an acquired node (only the nid field is used)
189
* @param $values Array of values to check against node_load. The node object must contain the keys in the array,
190
* and the values must be equal, but the node object may also contain other keys.
192
function assertNodeValues($node, $values) {
193
if (is_numeric($node) && isset($this->nodes[$node])) {
194
$node = $this->nodes[$node];
196
$node = node_load($node->nid, NULL, TRUE);
197
$this->_compareArrayForChanges($values, (array)$node, 'Node data [!key] is correct');
201
* Checks that the output from node_load is missing certain fields
202
* @param $node Either a node object, or the index of an acquired node (only the nid field is used)
203
* @param $fields Array containing a list of field names
205
function assertNodeMissingFields($node, $fields) {
206
if (is_numeric($node) && isset($this->nodes[$node])) {
207
$node = $this->nodes[$node];
209
$node = (array)node_load($node->nid, NULL, TRUE);
210
foreach ($fields as $field) {
211
$this->assertFalse(isset($node[$field]), t('Node should be lacking field !key', array('!key' => $field)));
216
* Creates random values for a text field
217
* @return An array containing a value key and a format key
219
function createRandomTextFieldData() {
221
'value' => '!SimpleTest! test value' . $this->randomName(60),
226
// Login/user helper functions
229
* Creates a user / role with certain permissions and then logs in as that user
230
* @param $permissions Array containing list of permissions. If not given, defaults to
231
* access content, administer content types, administer nodes and administer filters.
233
function loginWithPermissions($permissions = NULL) {
234
if (!isset($permissions)) {
235
$permissions = array(
237
'administer content types',
239
'administer filters',
242
$user = $this->drupalCreateUser($permissions);
243
$this->drupalLogin($user);
246
// Creation helper functions
249
* Creates a number of content types with predictable names (simpletest_t1 ... simpletest_tN)
250
* These content types can later be accessed via $this->content_types[0 ... N-1]
251
* @param $count Number of content types to create
253
function acquireContentTypes($count) {
254
$this->content_types = array();
255
for ($i = 0; $i < $count; $i++) {
256
$name = 'simpletest_t'. ($i + 1);
257
$this->content_types[$i] = $this->drupalCreateContentType(array(
262
content_clear_type_cache();
266
* Creates a number of nodes of each acquired content type.
267
* Remember to call acquireContentTypes() before calling this, else the content types won't exist.
268
* @param $count Number of nodes to create per acquired content type (defaults to 1)
270
function acquireNodes($count = 1) {
271
$this->nodes = array();
272
foreach ($this->content_types as $content_type) {
273
for ($i = 0; $i < $count; $i++) {
274
$this->nodes[] = $this->drupalCreateNode(array('type' => $content_type->type));
280
* Creates a field instance with a predictable name. Also makes all future calls to functions
281
* which take an optional field use this one as the default.
282
* @param $settings Array to be passed to content_field_instance_create. If the field_name
283
* or type_name keys are missing, then they will be added. The default field name is
284
* simpletest_fN, where N is 1 for the first created field, and increments. The default
285
* type name is type name of the $content_type argument.
286
* @param $content_type Either a content type object, or the index of an acquired content type
287
* @return The newly created field instance.
289
function createField($settings, $content_type = 0) {
290
if (is_numeric($content_type) && isset($this->content_types[$content_type])) {
291
$content_type = $this->content_types[$content_type];
294
'field_name' => 'simpletest_f'. $this->next_field_n++,
295
'type_name' => $content_type->type,
297
$settings = $settings + $defaults;
298
$this->last_field = content_field_instance_create($settings);
299
return $this->last_field;
303
* Creates a textfield instance. Identical to createField() except it ensures that the text module
304
* is enabled, and adds default settings of type (text) and widget_type (text_textfield) if they
305
* are not given in $settings.
308
function createFieldText($settings, $content_type = 0) {
311
'widget_type' => 'text_textfield',
313
$settings = $settings + $defaults;
314
return $this->createField($settings, $content_type);
317
// Field manipulation helper functions
320
* Updates a field instance. Also makes all future calls to functions which take an optional
321
* field use the updated one as the default.
322
* @param $settings New settings for the field instance. If the field_name or type_name keys
323
* are missing, then they will be taken from $field.
324
* @param $field The field instance to update (defaults to the last worked upon field)
325
* @return The updated field instance.
327
function updateField($settings, $field = NULL) {
328
if (!isset($field)) {
329
$field = $this->last_field;
332
'field_name' => $field['field_name'],
333
'type_name' => $field['type_name'] ,
335
$settings = $settings + $defaults;
336
$this->last_field = content_field_instance_update($settings);
337
return $this->last_field;
341
* Makes a copy of a field instance on a different content type, effectively sharing the field with a new
342
* content type. Also makes all future calls to functions which take an optional field use the shared one
344
* @param $new_content_type Either a content type object, or the index of an acquired content type
345
* @param $field The field instance to share (defaults to the last worked upon field)
346
* @return The shared (newly created) field instance.
348
function shareField($new_content_type, $field = NULL) {
349
if (!isset($field)) {
350
$field = $this->last_field;
352
if (is_numeric($new_content_type) && isset($this->content_types[$new_content_type])) {
353
$new_content_type = $this->content_types[$new_content_type];
355
$field['type_name'] = $new_content_type->type;
356
$this->last_field = content_field_instance_create($field);
357
return $this->last_field;
361
* Deletes an instance of a field.
362
* @param $content_type Either a content type object, or the index of an acquired content type (used only
363
* to get field instance type name).
364
* @param $field The field instance to delete (defaults to the last worked upon field, used only to get
365
* field instance field name).
367
function deleteField($content_type, $field = NULL) {
368
if (!isset($field)) {
369
$field = $this->last_field;
371
if (is_numeric($content_type) && isset($this->content_types[$content_type])) {
372
$content_type = $this->content_types[$content_type];
374
content_field_instance_delete($field['field_name'], $content_type->type);
378
class ContentCrudBasicTest extends ContentCrudTestCase {
381
'name' => t('CRUD - Basic API tests'),
382
'description' => t('Tests the field CRUD (create, read, update, delete) API. <strong>Requires <a href="@schema_link">Schema module</a>.</strong>', array('@schema_link' => 'http://www.drupal.org/project/schema')),
389
$this->acquireContentTypes(1);
392
function testBasic() {
393
// Create a field with both field and instance settings.
394
$field = $this->createFieldText(array('widget_type' => 'text_textarea', 'text_processing' => 1, 'rows' => 5), 0);
397
// Check that collapse and expand are inverse.
398
$fields = content_field_instance_read(array('field_name' => $field['field_name'], 'type_name' => $this->content_types[0]->type));
399
$field1 = array_pop($fields);
401
$field2 = content_field_instance_collapse($field1);
402
$field3 = content_field_instance_expand($field2);
403
$field4 = content_field_instance_collapse($field3);
405
$this->assertIdentical($field1, $field3, 'collapse then expand is identity');
406
$this->assertIdentical($field2, $field4, 'expand then collapse is identity');
409
// Check that collapse and expand are both final
410
// (e.g. do not further alter the data when called multiple times).
411
$fields = content_field_instance_read(array('field_name' => $field['field_name'], 'type_name' => $this->content_types[0]->type));
412
$field1 = array_pop($fields);
414
$field2 = content_field_instance_collapse($field1);
415
$field3 = content_field_instance_collapse($field2);
416
$this->assertIdentical($field2, $field3, 'collapse is final');
418
$field2 = content_field_instance_expand($field1);
419
$field3 = content_field_instance_expand($field2);
420
$this->assertIdentical($field2, $field3, 'expand is final');
423
// Check that updating a field as is leaves it unchanged.
424
$fields = content_field_instance_read(array('field_name' => $field['field_name'], 'type_name' => $this->content_types[0]->type));
425
$field1 = array_pop($fields);
426
$field2 = content_field_instance_update($field1);
427
$fields = content_field_instance_read(array('field_name' => $field['field_name'], 'type_name' => $this->content_types[0]->type));
428
$field3 = array_pop($fields);
430
$this->assertIdentical($field1, $field3, 'read, update, read is identity');
434
class ContentCrudSingleToMultipleTest extends ContentCrudTestCase {
437
'name' => t('CRUD - Single to multiple'),
438
'description' => t('Tests the field CRUD (create, read, update, delete) API by creating a single value field and changing it to a multivalue field, sharing it between several content types. <strong>Requires <a href="@schema_link">Schema module</a>.</strong>', array('@schema_link' => 'http://www.drupal.org/project/schema')),
445
$this->loginWithPermissions();
446
$this->acquireContentTypes(3);
447
$this->acquireNodes();
450
function testSingleToMultiple() {
451
// Create a simple text field
452
$this->createFieldText(array('text_processing' => 1));
453
$target_schema = array(
455
'simpletest_t1' => array('simpletest_f1' => array('value', 'format'))
457
'per_field' => array(),
459
$this->assertSchemaMatchesTables($target_schema);
460
$node0values = $this->assertNodeSaveValues(0, array(
461
'simpletest_f1' => array(
462
0 => $this->createRandomTextFieldData(),
466
// Change the text field to allow multiple values
467
$this->updateField(array('multiple' => 1));
468
$target_schema = array(
470
'simpletest_t1' => array(),
472
'per_field' => array(
473
'simpletest_f1' => array('delta', 'simpletest_f1' => array('value', 'format')),
476
$this->assertSchemaMatchesTables($target_schema);
477
$this->assertNodeValues(0, $node0values);
479
// Share the text field with 2 additional types t2 and t3.
480
for ($share_with_content_type = 1; $share_with_content_type <= 2; $share_with_content_type++) {
481
$this->shareField($share_with_content_type);
482
// There should be a new 'empty' per-type table for each content type that has fields.
483
$target_schema['per_type']['simpletest_t'. ($share_with_content_type + 1)] = array();
484
$this->assertSchemaMatchesTables($target_schema);
485
// The acquired node index will match the content type index as exactly one node is acquired per content type
486
$this->assertNodeSaveValues($share_with_content_type, array(
487
'simpletest_f1' => array(
488
0 => $this->createRandomTextFieldData(),
493
// Delete the text field from all content types
494
for ($delete_from_content_type = 2; $delete_from_content_type >= 0; $delete_from_content_type--) {
495
$this->deleteField($delete_from_content_type);
496
// Content types that don't have fields any more shouldn't have any per-type table.
497
$target_schema['per_type']['simpletest_t'. ($delete_from_content_type + 1)] = NULL;
498
// After removing the last instance, there should be no table for the field either.
499
if ($delete_from_content_type == 0) {
500
$target_schema['per_field']['simpletest_f1'] = NULL;
502
$this->assertSchemaMatchesTables($target_schema);
503
// The acquired node index will match the content type index as exactly one node is acquired per content type
504
$this->assertNodeMissingFields($this->nodes[$delete_from_content_type], array('simpletest_f1'));
509
class ContentCrudMultipleToSingleTest extends ContentCrudTestCase {
512
'name' => t('CRUD - Multiple to single'),
513
'description' => t('Tests the field CRUD (create, read, update, delete) API by creating a multivalue field and changing it to a single value field, sharing it between several content types. <strong>Requires <a href="@schema_link">Schema module</a>.</strong>', array('@schema_link' => 'http://www.drupal.org/project/schema')),
520
$this->loginWithPermissions();
521
$this->acquireContentTypes(3);
522
$this->acquireNodes();
525
function testMultipleToSingle() {
526
// Create a multivalue text field
527
$this->createFieldText(array('text_processing' => 1, 'multiple' => 1));
528
$this->assertSchemaMatchesTables(array(
530
'simpletest_t1' => array(),
532
'per_field' => array(
533
'simpletest_f1' => array('delta', 'simpletest_f1' => array('value', 'format')),
536
$this->assertNodeSaveValues(0, array(
537
'simpletest_f1' => array(
538
0 => $this->createRandomTextFieldData(),
539
1 => $this->createRandomTextFieldData(),
540
2 => $this->createRandomTextFieldData(),
544
// Change to a simple text field
545
$this->updateField(array('multiple' => 0));
546
$this->assertSchemaMatchesTables(array(
548
'simpletest_t1' => array('simpletest_f1' => array('value', 'format')),
550
'per_field' => array(
551
'simpletest_f1' => NULL,
554
$node0values = $this->assertNodeSaveValues(0, array(
555
'simpletest_f1' => array(
556
0 => $this->createRandomTextFieldData(),
560
// Share the text field with other content type
561
$this->shareField(1);
562
$this->assertSchemaMatchesTables(array(
564
'simpletest_t1' => array(),
565
'simpletest_t2' => array(),
567
'per_field' => array(
568
'simpletest_f1' => array('simpletest_f1' => array('value', 'format')),
571
$node1values = $this->assertNodeSaveValues(1, array(
572
'simpletest_f1' => array(
573
0 => $this->createRandomTextFieldData(),
576
$this->assertNodeValues(0, $node0values);
578
// Share the text field with a 3rd type
579
$this->shareField(2);
580
$this->assertSchemaMatchesTables(array(
582
'simpletest_t1' => array(),
583
'simpletest_t2' => array(),
584
'simpletest_t3' => array(),
586
'per_field' => array(
587
'simpletest_f1' => array('simpletest_f1' => array('value', 'format')),
590
$this->assertNodeSaveValues(2, array(
591
'simpletest_f1' => array(
592
0 => $this->createRandomTextFieldData(),
595
$this->assertNodeValues(1, $node1values);
596
$this->assertNodeValues(0, $node0values);
598
// Remove text field from 3rd type
599
$this->deleteField(2);
600
$this->assertSchemaMatchesTables(array(
602
'simpletest_t1' => array(),
603
'simpletest_t2' => array(),
604
'simpletest_t3' => NULL,
606
'per_field' => array(
607
'simpletest_f1' => array('simpletest_f1' => array('value', 'format')),
610
$this->assertNodeMissingFields($this->nodes[2], array('simpletest_f1'));
612
// Remove text field from 2nd type (field isn't shared anymore)
613
$this->deleteField(1);
614
$this->assertSchemaMatchesTables(array(
616
'simpletest_t1' => array('simpletest_f1' => array('value', 'format')),
617
'simpletest_t2' => NULL,
618
'simpletest_t3' => NULL,
620
'per_field' => array(
621
'simpletest_f1' => NULL,
624
$this->assertNodeMissingFields(1, array('simpletest_f1'));
625
$this->assertNodeValues(0, $node0values);
627
// Remove text field from original type
628
$this->deleteField(0);
629
$this->assertSchemaMatchesTables(array(
631
'simpletest_t1' => NULL,
632
'simpletest_t2' => NULL,
633
'simpletest_t3' => NULL,
635
'per_field' => array(
636
'simpletest_f1' => NULL,
639
$this->assertNodeMissingFields(0, array('simpletest_f1'));
643
class ContentUICrud extends ContentCrudTestCase {
646
'name' => t('Admin UI'),
647
'description' => t('Tests the CRUD (create, read, update, delete) operations for content fields via the UI. <strong>Requires <a href="@schema_link">Schema module</a>.</strong>', array('@schema_link' => 'http://www.drupal.org/project/schema')),
653
parent::setUp('fieldgroup');
654
$this->loginWithPermissions();
657
function testAddFieldUI() {
658
// Add a content type with a random name (to avoid schema module problems).
659
$type1 = 'simpletest'. mt_rand();
660
$type1_name = $this->randomName(10);
663
'name' => $type1_name,
665
$this->drupalPost('admin/content/types/add', $edit, 'Save content type');
666
$admin_type1_url = 'admin/content/node-type/'. $type1;
668
// Create a text field via the UI.
669
$field_name = strtolower($this->randomName(10));
670
$field_label = $this->randomName(10);
672
'_add_new_field[label]' => $field_label,
673
'_add_new_field[field_name]' => $field_name,
674
'_add_new_field[type]' => 'text',
675
'_add_new_field[widget_type]' => 'text_textfield',
677
$this->drupalPost($admin_type1_url .'/fields', $edit, 'Save');
678
$this->assertRaw('These settings apply only to the <em>'. $field_label .'</em> field', 'Field settings page displayed');
679
$this->assertRaw('Size of textfield', 'Field and widget types correct.');
680
$this->assertNoRaw('Change basic information', 'No basic information displayed');
681
$field_name = 'field_'. $field_name;
684
// POST to the page without reloading.
685
$this->drupalPost(NULL, $edit, 'Save field settings');
686
$this->assertRaw('Added field <em>'. $field_label .'</em>.', 'Field settings saved');
687
$field_type1_url = $admin_type1_url .'/fields/'. $field_name;
688
$this->assertRaw($field_type1_url, 'Field displayed on overview.');
690
// Check the schema - the values should be in the per-type table.
691
$this->assertSchemaMatchesTables(array(
693
$type1 => array($field_name => array('value')),
698
// Add a second content type.
699
$type2 = 'simpletest'. mt_rand();
700
$type2_name = $this->randomName(10);
703
'name' => $type2_name,
705
$this->drupalPost('admin/content/types/add', $edit, 'Save content type');
706
$admin_type2_url = 'admin/content/node-type/'. $type2;
708
// Add the same field to the second content type.
710
'_add_existing_field[label]' => $field_label,
711
'_add_existing_field[field_name]' => $field_name,
712
'_add_existing_field[widget_type]' => 'text_textarea',
714
$this->drupalPost($admin_type2_url .'/fields', $edit, 'Save');
715
$this->assertRaw('These settings apply only to the <em>'. $field_label .'</em> field', 'Field settings page displayed');
716
$this->assertRaw('Rows', 'Field and widget types correct.');
717
$this->assertNoRaw('Change basic information', 'No basic information displayed');
720
$this->drupalPost(NULL, $edit, 'Save field settings');
721
$this->assertRaw('Added field <em>'. $field_label .'</em>.', 'Field settings saved');
722
$field_type2_url = $admin_type2_url .'/fields/'. $field_name;
723
$this->assertRaw($field_type2_url, 'Field displayed on overview.');
725
// Check that a separate table is created for the shared field, and
726
// that it's values are no longer in the per-type tables.
727
$this->assertSchemaMatchesTables(array(
728
'per_field' => array(
729
$field_name => array($field_name => array('value')),
738
// Chancge the basic settings for this field.
740
$this->drupalPost($field_type2_url, $edit, 'Change basic information');
741
$this->assertRaw('Edit basic information', 'Basic information form displayed');
743
$field_label2 = $this->randomName(10);
745
'label' => $field_label2,
746
'widget_type' => 'text_textfield',
748
$this->drupalPost(NULL, $edit, 'Continue');
749
$this->assertRaw('These settings apply only to the <em>'. $field_label2 .'</em> field', 'Label changed');
750
$this->assertRaw('Size of textfield', 'Widget changed');
753
// POST to the page without reloading.
754
$this->drupalPost(NULL, $edit, 'Save field settings');
755
$this->assertRaw('Saved field <em>'. $field_label2 .'</em>.', 'Field settings saved');
758
// Add a group to the second content type.
759
$group1_name = strtolower($this->randomName(10));
760
$group1_label = $this->randomName(10);
762
'_add_new_group[label]' => $group1_label,
763
'_add_new_group[group_name]' => $group1_name,
765
$this->drupalPost($admin_type2_url .'/fields', $edit, 'Save');
766
$group1_name = 'group_'. $group1_name;
767
$this->assertRaw($admin_type2_url .'/groups/'. $group1_name, 'Group created');
770
// Remove the field from the second type.
772
$this->drupalPost($field_type2_url .'/remove', $edit, 'Remove');
773
$this->assertRaw('Removed field <em>'. $field_label2 .'</em> from <em>'. $type2_name .'</em>', 'Field removed');
774
$this->assertNoRaw($field_type2_url, 'Field not displayed on overview.');
776
// Check the schema - the values should be in the per-type table.
777
$this->assertSchemaMatchesTables(array(
779
$type1 => array($field_name => array('value')),
783
// Add a new field, an existing field, and a group in the same submit.
784
$field2_label = $this->randomName(10);
785
$field2_name = strtolower($this->randomName(10));
786
$group2_label = $this->randomName(10);
787
$group2_name = strtolower($this->randomName(10));
789
'_add_new_field[label]' => $field2_label,
790
'_add_new_field[field_name]' => $field2_name,
791
'_add_new_field[type]' => 'text',
792
'_add_new_field[widget_type]' => 'text_textfield',
793
'_add_new_field[parent]' => $group1_name,
794
'_add_existing_field[label]' => $field_label,
795
'_add_existing_field[field_name]' => $field_name,
796
'_add_existing_field[widget_type]' => 'text_textarea',
797
'_add_existing_field[parent]' => '_add_new_group',
798
'_add_new_group[label]' => $group2_label,
799
'_add_new_group[group_name]' => $group2_name,
801
$this->drupalPost($admin_type2_url .'/fields', $edit, 'Save');
802
$this->assertRaw('These settings apply only to the <em>'. $field2_label .'</em> field', 'Field settings page for new field displayed');
803
// Submit new field settings
805
$this->drupalPost(NULL, $edit, 'Save field settings');
806
$this->assertRaw('Added field <em>'. $field2_label .'</em>.', 'Field settings for new field saved');
807
$this->assertRaw('These settings apply only to the <em>'. $field_label .'</em> field', 'Field settings page for existing field displayed');
808
// Submit existing field settings
810
$this->drupalPost(NULL, $edit, 'Save field settings');
811
$this->assertRaw('Added field <em>'. $field_label .'</em>.', 'Field settings for existing field saved');
812
$field2_name = 'field_'. $field2_name;
813
$field2_type2_url = $admin_type2_url .'/fields/'. $field2_name;
814
$this->assertRaw($field2_type2_url, 'New field displayed in overview');
815
$this->assertRaw($field_type2_url, 'Existing field displayed in overview');
816
$group2_name = 'group_'. $group2_name;
817
$this->assertRaw($admin_type2_url .'/groups/'. $group2_name, 'New group displayed in overview');
820
$groups = fieldgroup_groups($type2, FALSE, TRUE);
821
$this->assertTrue(isset($groups[$group1_name]['fields'][$field2_name]), 'New field in correct group');
822
$this->assertTrue(isset($groups[$group2_name]['fields'][$field_name]), 'Existing field in correct group');
823
$this->assertFieldByXPath('//select[@id="edit-'. strtr($field2_name, '_', '-') .'-parent"]//option[@selected]', $group1_name, 'Parenting for new field correct in overview');
824
$this->assertFieldByXPath('//select[@id="edit-'. strtr($field_name, '_', '-') .'-parent"]//option[@selected]', $group2_name, 'Parenting for existing field correct in overview');
826
// Check the schema : field1 is shared, field2 is in the per-type table.
827
$this->assertSchemaMatchesTables(array(
828
'per_field' => array(
829
$field_name => array($field_name => array('value')),
833
$type2 => array($field2_name => array('value')),
837
// TODO : test validation failures...
838
// TODO : test ordering and extra fields...
841
function testFieldContentUI() {
842
// Create a content type with a field
843
$type1 = 'simpletest'. mt_rand();
844
$type1_obj = $this->drupalCreateContentType(array('type' => $type1));
845
$admin_type1_url = 'admin/content/node-type/'. $type1;
846
$field_name = strtolower($this->randomName(10));
847
$field_url = 'field_'. $field_name;
848
$field = $this->createFieldText(array('text_processing' => 1, 'multiple' => 0, 'field_name' => $field_url), $type1_obj);
850
// Save a node with content in the text field
852
$edit['title'] = $this->randomName(20);
853
$edit['body'] = $this->randomName(20);
854
$value = $this->randomName(20);
855
$edit[$field_url.'[0][value]'] = $value;
856
$this->drupalPost('node/add/'. $type1, $edit, 'Save');
857
$node = node_load(array('title' => $edit['title']));
858
$this->drupalGet('node/'. $node->nid);
859
$this->assertText($value, 'Textfield value saved and displayed');
861
// Alter the field to have unlimited values
863
$edit['multiple'] = '1';
864
$this->drupalPost($admin_type1_url .'/fields/'. $field_url, $edit, 'Save field settings');
866
// Save a node with content in multiple text fields
868
$edit['title'] = $this->randomName(20);
869
$edit['body'] = $this->randomName(20);
870
// Add more textfields (non-JS).
871
$this->drupalPost('node/add/'. $type1, $edit, "Add another item");
872
$this->drupalPost(NULL, $edit, "Add another item");
873
$value1 = $this->randomName(20);
874
$value2 = $this->randomName(20);
875
$value3 = $this->randomName(20);
876
$edit[$field_url.'[0][value]'] = $value1;
877
$edit[$field_url.'[1][value]'] = $value2;
878
$edit[$field_url.'[2][value]'] = $value3;
880
// This will fail if we don't have at least 3 textfields.
881
$this->drupalPost(NULL, $edit, 'Save');
882
$node = node_load(array('title' => $edit['title']));
883
$this->drupalGet('node/'. $node->nid);
884
$this->assertText($value3, '3rd textfield value saved and displayed');
888
class ContentOptionWidgetTest extends ContentCrudTestCase {
891
'name' => t('Option widgets'),
892
'description' => t('Tests the optionwidgets.'),
898
parent::setUp('optionwidgets');
899
$this->loginWithPermissions();
900
$this->acquireContentTypes(1);
903
// TODO: test a number field with optionwidgets stores 0 correctly ?
904
// TODO: test the case where aliases and values overlap ? (http://drupal.org/node/281749)
905
// TODO: test noderef select widget...
908
* On/Off Checkbox, not required:
909
* - Create a node with the value checked.
910
* - FAILS: Edit the node and uncheck the value.
912
* On/Off Checkbox, required:
913
* - TODO: what behavior do we want ?
915
function testOnOffCheckbox() {
916
$type = $this->content_types[0];
917
$type_url = str_replace('_', '-', $type->type);
920
$on_text = $this->randomName(5);
921
$on_value = $this->randomName(5);
922
$off_text = $on_text. '_off';
923
$off_value = $on_value. '_off';
927
'widget_type' => 'optionwidgets_onoff',
928
'allowed_values' => "$off_value|$off_text\r\n$on_value|$on_text",
930
$field = $this->createField($settings, 0);
931
$field_name = $field['field_name'];
933
// Create a node with the checkbox on.
935
'title' => $this->randomName(20),
936
'body' => $this->randomName(20),
937
$field_name.'[value]' => $on_value,
939
$this->drupalPost('node/add/'. $type_url, $edit, 'Save');
940
$node = node_load(array('title' => $edit['title']));
941
$this->assertEqual($node->{$field_name}[0]['value'], $on_value, 'Checkbox: checked (saved)');
942
$this->drupalGet('node/'. $node->nid);
943
$this->assertText($on_text, 'Checkbox: checked (displayed)');
945
// Edit the node and uncheck the box.
947
$field_name.'[value]' => FALSE,
949
$this->drupalPost('node/'. $node->nid .'/edit', $edit, 'Save');
950
$node = node_load($node->nid, NULL, TRUE);
951
$this->assertEqual($node->{$field_name}[0]['value'], $off_value, 'Checkbox: unchecked (saved)');
952
$this->drupalGet('node/'. $node->nid);
953
$this->assertText($off_text, 'Checkbox: unchecked (displayed)');
957
* Single select, not required:
958
* - TODO: check there's a 'none' choice in the form.
959
* - Create a node with one value selected.
960
* - Edit the node and unselect the value (selecting '- None -').
962
* Single select, required:
963
* - TODO: check there's no 'none' choice in the form.
965
* Multiple select, not required:
966
* - TODO: check there's a 'none' choice in the form.
967
* - Edit the node and select multiple values.
968
* - Edit the node and unselect one value.
969
* - Edit the node and unselect the values (selecting '- None -').
970
* - Edit the node and unselect the values (selecting nothing).
972
* Multiple select, required:
973
* - TODO: check there's no 'none' choice in the form.
974
* - Check the form doesn't submit when nothing is selected.
976
function testSelect() {
977
$type = $this->content_types[0];
978
$type_url = str_replace('_', '-', $type->type);
980
// Create the field - start with 'single'.
981
$value1 = $this->randomName(5);
982
$value1_alias = $value1 .'_alias';
983
$value2 = $this->randomName(5);
984
$value2_alias = $value2 .'_alias';
988
'widget_type' => 'optionwidgets_select',
989
'allowed_values' => "$value1|$value1_alias\r\n$value2|$value2_alias",
991
$field = $this->createField($settings, 0);
992
$field_name = $field['field_name'];
994
// Create a node with one value selected
996
'title' => $this->randomName(20),
997
'body' => $this->randomName(20),
999
$edit[$field_name.'[value]'] = $value1;
1000
$this->drupalPost('node/add/'. $type_url, $edit, 'Save');
1001
$node = node_load(array('title' => $edit['title']));
1002
$this->assertEqual($node->{$field_name}[0]['value'], $value1, 'Select: selected (saved)');
1003
$this->drupalGet('node/'. $node->nid);
1004
$this->assertText($value1_alias, 'Select: selected (displayed)');
1006
// Edit the node and unselect the value (selecting '- None -').
1008
$field_name.'[value]' => '',
1010
$this->drupalPost('node/'. $node->nid .'/edit', $edit, 'Save');
1011
$node = node_load($node->nid, NULL, TRUE);
1012
$this->assertIdentical($node->{$field_name}[0]['value'], NULL, 'Select: unselected (saved)');
1013
$this->drupalGet('node/'. $node->nid);
1014
$this->assertNoText($value1_alias, 'Select: unselected (displayed)');
1016
// Change to a multiple field
1017
$field = $this->updateField(array('multiple' => '1', 'required' => '0'));
1019
// Edit the node and select multiple values.
1021
$field_name.'[value][]' => array($value1 => $value1, $value2 => $value2),
1023
$this->drupalPost('node/'. $node->nid .'/edit', $edit, 'Save');
1024
$node = node_load($node->nid, NULL, TRUE);
1025
$this->assertEqual($node->{$field_name}[0]['value'], $value1, 'Multiple Select: selected 1 (saved)');
1026
$this->assertEqual($node->{$field_name}[1]['value'], $value2, 'Multiple Select: selected 2 (saved)');
1027
$this->drupalGet('node/'. $node->nid);
1028
$this->assertText($value1_alias, 'Multiple Select: selected 1 (displayed)');
1029
$this->assertText($value2_alias, 'Multiple Select: selected 2 (displayed)');
1031
// Edit the node and unselect one value.
1033
$field_name.'[value][]' => array($value1 => $value1),
1035
$this->drupalPost('node/'. $node->nid .'/edit', $edit, 'Save');
1036
$node = node_load($node->nid, NULL, TRUE);
1037
$this->assertEqual($node->{$field_name}[0]['value'], $value1, 'Multiple Select: selected 1 (saved)');
1038
$this->assertTrue(!isset($node->{$field_name}[1]), 'Multiple Select: unselected 2 (saved)');
1039
$this->drupalGet('node/'. $node->nid);
1040
$this->assertText($value1_alias, 'Multiple Select: selected 1 (displayed)');
1041
$this->assertNoText($value2_alias, 'Multiple Select: unselected 2 (displayed)');
1043
// Edit the node and unselect the values (selecting '- None -').
1045
$field_name.'[value][]' => array('' => ''),
1047
$this->drupalPost('node/'. $node->nid .'/edit', $edit, 'Save');
1048
$node = node_load($node->nid, NULL, TRUE);
1049
$this->assertIdentical($node->{$field_name}[0]['value'], NULL, 'Multiple Select: unselected 1 ("-none-" selected) (saved)');
1050
$this->assertTrue(!isset($node->{$field_name}[1]), 'Multiple Select: unselected 2 ("-none-" selected) (saved)');
1051
$this->drupalGet('node/'. $node->nid);
1052
$this->assertNoText($value1_alias, 'Multiple Select: unselected 1 ("-none-" selected) (displayed)');
1053
$this->assertNoText($value2_alias, 'Multiple Select: unselected 2 ("-none-" selected) (displayed)');
1055
// Edit the node and unselect the values (selecting nothing).
1056
// We first need to put values back in (no test needed).
1058
$edit[$field_name.'[value][]'] = array($value1 => FALSE, $value2 => FALSE);
1059
$this->drupalPost('node/'. $node->nid .'/edit', $edit, 'Save');
1061
$this->drupalPost('node/'. $node->nid .'/edit', $edit, 'Save');
1062
$node = node_load($node->nid, NULL, TRUE);
1063
$this->assertIdentical($node->{$field_name}[0]['value'], NULL, 'Multiple Select: unselected 1 (no selection) (saved)');
1064
$this->assertTrue(!isset($node->{$field_name}[1]), 'Multiple Select: unselected 2 (no selection) (saved)');
1065
$this->drupalGet('node/'. $node->nid);
1066
$this->assertNoText($value1_alias, 'Multiple Select: unselected 1 (no selection) (displayed)');
1067
$this->assertNoText($value2_alias, 'Multiple Select: unselected 2 (no selection) (displayed)');
1069
// Change the field to 'required'.
1070
$field = $this->updateField(array('required' => '1'));
1072
// Check the form doesn't submit when nothing is selected.
1074
$this->drupalPost('node/'. $node->nid .'/edit', $edit, 'Save');
1075
$this->assertRaw(t('!name field is required.', array('!name' => t($field['widget']['label']))), 'Multiple Select: "required" property is respected');
1078
'title' => $this->randomName(20),
1079
'body' => $this->randomName(20),
1081
$this->drupalPost('node/add/'. $type_url, $edit, 'Save');
1082
$this->assertRaw(t('!name field is required.', array('!name' => t($field['widget']['label']))), 'Multiple Select: "required" property is respected');
1087
* Single (radios), not required:
1088
* - TODO: check there's a 'none' choice in the form.
1089
* - Create a node with one value selected.
1090
* - Edit the node and unselect the value (selecting '- None -').
1092
* Single (radios), required:
1093
* - TODO: check there's no 'none' choice in the form.
1094
* - Check the form doesn't submit when nothing is selected.
1096
function testRadios() {
1097
$type = $this->content_types[0];
1098
$type_url = str_replace('_', '-', $type->type);
1100
// Create the field - 'single' (radios).
1101
$value1 = $this->randomName(5);
1102
$value1_alias = $value1 .'_alias';
1103
$value2 = $this->randomName(5);
1104
$value2_alias = $value2 .'_alias';
1107
'widget_type' => 'optionwidgets_buttons',
1108
'allowed_values' => "$value1|$value1_alias\r\n$value2|$value2_alias",
1110
$field = $this->createField($settings, 0);
1111
$field_name = $field['field_name'];
1113
// Create a node with one value selected
1115
$edit['title'] = $this->randomName(20);
1116
$edit['body'] = $this->randomName(20);
1117
$edit[$field_name.'[value]'] = $value1;
1118
$this->drupalPost('node/add/'. $type_url, $edit, 'Save');
1119
$node = node_load(array('title' => $edit['title']));
1120
$this->assertEqual($node->{$field_name}[0]['value'], $value1, 'Radios: checked (saved)');
1121
$this->drupalGet('node/'. $node->nid);
1122
$this->assertText($value1_alias, 'Radios: checked (displayed)');
1124
// Edit the node and unselect the value (selecting '- None -').
1126
$edit[$field_name.'[value]'] = '';
1127
$this->drupalPost('node/'. $node->nid .'/edit', $edit, 'Save');
1128
$node = node_load($node->nid, NULL, TRUE);
1129
$this->assertIdentical($node->{$field_name}[0]['value'], NULL, 'Radios: unchecked (saved)');
1130
$this->drupalGet('node/'. $node->nid);
1131
$this->assertNoText($value1_alias, 'Radios: unchecked (displayed)');
1133
// Change field to required.
1134
$field = $this->updateField(array('required' => '1'));
1136
// Check the form doesn't submit when nothing is selected.
1137
// Doing this on the pre-filled node doesn't take, so we test that on a new node.
1139
$edit['title'] = $this->randomName(20);
1140
$edit['body'] = $this->randomName(20);
1141
$this->drupalPost('node/add/'. $type_url, $edit, 'Save');
1142
$this->assertRaw(t('!name field is required.', array('!name' => t($field['widget']['label']))), 'Radios: "required" property is respected');
1146
* Multiple (checkboxes), not required:
1147
* - TODO: check there's no 'none' choice in the form.
1148
* - Create a node with two values.
1149
* - Edit the node and select only one value.
1150
* - Edit the node and unselect the values (selecting nothing).
1152
* Multiple (checkboxes), required:
1153
* - TODO: check there's no 'none' choice in the form.
1154
* - Check the form doesn't submit when nothing is selected.
1156
function testChecboxes() {
1157
$type = $this->content_types[0];
1158
$type_url = str_replace('_', '-', $type->type);
1160
// Create the field - 'multiple' (checkboxes).
1161
$value1 = $this->randomName(5);
1162
$value1_alias = $value1 .'_alias';
1163
$value2 = $this->randomName(5);
1164
$value2_alias = $value2 .'_alias';
1168
'widget_type' => 'optionwidgets_buttons',
1169
'allowed_values' => "$value1|$value1_alias\r\n$value2|$value2_alias",
1171
$field = $this->createField($settings, 0);
1172
$field_name = $field['field_name'];
1174
// Create a node with two values selected
1176
'title' => $this->randomName(20),
1177
'body' => $this->randomName(20),
1178
$field_name.'[value]['. $value1 .']' => $value1,
1179
$field_name.'[value]['. $value2 .']' => $value2,
1181
$this->drupalPost('node/add/'. $type_url, $edit, 'Save');
1182
$node = node_load(array('title' => $edit['title']));
1183
$this->assertEqual($node->{$field_name}[0]['value'], $value1, 'Checkboxes: selected 1 (saved)');
1184
$this->assertEqual($node->{$field_name}[1]['value'], $value2, 'Checkboxes: selected 2 (saved)');
1185
$this->drupalGet('node/'. $node->nid);
1186
$this->assertText($value1_alias, 'Checkboxes: selected 1 (displayed)');
1187
$this->assertText($value2_alias, 'Checkboxes: selected 2 (displayed)');
1189
// Edit the node and unselect the values (selecting nothing -
1190
// there is no 'none' choice for checkboxes).
1192
$field_name.'[value]['. $value1 .']' => $value1,
1193
$field_name.'[value]['. $value2 .']' => FALSE,
1195
$this->drupalPost('node/'. $node->nid .'/edit', $edit, 'Save');
1196
$node = node_load($node->nid, NULL, TRUE);
1197
$this->assertEqual($node->{$field_name}[0]['value'], $value1, 'Checkboxes: selected 1 (saved)');
1198
$this->assertTrue(!isset($node->{$field_name}[1]), 'Checkboxes: unselected 2 (saved)');
1199
$this->drupalGet('node/'. $node->nid);
1200
$this->assertText($value1_alias, 'Checkboxes: selected 1 (displayed)');
1201
$this->assertNoText($value2_alias, 'Checkboxes: unselected 2 (displayed)');
1203
// Edit the node and unselect the values (selecting nothing -
1204
// there is no 'none' choice for checkboxes).
1206
$field_name.'[value]['. $value1 .']' => FALSE,
1207
$field_name.'[value]['. $value2 .']' => FALSE,
1209
$this->drupalPost('node/'. $node->nid .'/edit', $edit, 'Save');
1210
$node = node_load($node->nid, NULL, TRUE);
1211
$this->assertIdentical($node->{$field_name}[0]['value'], NULL, 'Checkboxes: unselected 1 (no selection) (saved)');
1212
$this->assertTrue(!isset($node->{$field_name}[1]), 'Checkboxes: unselected 2 (no selection) (saved)');
1213
$this->drupalGet('node/'. $node->nid);
1214
$this->assertNoText($value1_alias, 'Checkboxes: unselected 1 (no selection) (displayed)');
1215
$this->assertNoText($value2_alias, 'Checkboxes: unselected 2 (no selection) (displayed)');
1217
// Change field to required.
1218
$field = $this->updateField(array('required' => '1'));
1220
// Check the form doesn't submit when nothing is selected.
1222
$field_name.'[value]['. $value1 .']' => FALSE,
1223
$field_name.'[value]['. $value2 .']' => FALSE,
1225
$this->drupalPost('node/'. $node->nid .'/edit', $edit, 'Save');
1226
$this->assertRaw(t('!name field is required.', array('!name' => t($field['widget']['label']))), 'Checkboxes: "required" property is respected');
1229
$edit['title'] = $this->randomName(20);
1230
$edit['body'] = $this->randomName(20);
1231
$this->drupalPost('node/add/'. $type_url, $edit, 'Save');
1232
$this->assertRaw(t('!name field is required.', array('!name' => t($field['widget']['label']))), 'Checkboxes: "required" property is respected');