? .cache
? .settings
Index: modules/field/modules/list/list.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/list/list.test,v
retrieving revision 1.1
diff -u -p -r1.1 list.test
--- modules/field/modules/list/list.test	18 Oct 2009 18:46:11 -0000	1.1
+++ modules/field/modules/list/list.test	18 Nov 2009 12:02:31 -0000
@@ -42,28 +42,28 @@ class ListFieldTestCase extends DrupalWe
     // All three options appear.
     $entity = field_test_create_stub_entity(0, 0, FIELD_TEST_BUNDLE);
     $form = drupal_get_form('field_test_entity_form', $entity);
-    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE]['value'][1]), t('Option 1 exists'));
-    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE]['value'][2]), t('Option 2 exists'));
-    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE]['value'][3]), t('Option 3 exists'));
+    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE][1]), t('Option 1 exists'));
+    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE][2]), t('Option 2 exists'));
+    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE][3]), t('Option 3 exists'));
 
     // Removed options do not appear.
     $this->card_1['settings']['allowed_values'] = "2|Two";
     field_update_field($this->card_1);
     $entity = field_test_create_stub_entity(0, 0, FIELD_TEST_BUNDLE);
     $form = drupal_get_form('field_test_entity_form', $entity);
-    $this->assertTrue(empty($form['card_1'][FIELD_LANGUAGE_NONE]['value'][1]), t('Option 1 does not exist'));
-    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE]['value'][2]), t('Option 2 exists'));
-    $this->assertTrue(empty($form['card_1'][FIELD_LANGUAGE_NONE]['value'][3]), t('Option 3 does not exist'));
+    $this->assertTrue(empty($form['card_1'][FIELD_LANGUAGE_NONE][1]), t('Option 1 does not exist'));
+    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE][2]), t('Option 2 exists'));
+    $this->assertTrue(empty($form['card_1'][FIELD_LANGUAGE_NONE][3]), t('Option 3 does not exist'));
 
     // Completely new options appear.
     $this->card_1['settings']['allowed_values'] = "10|Update\n20|Twenty";
     field_update_field($this->card_1);
     $form = drupal_get_form('field_test_entity_form', $entity);
-    $this->assertTrue(empty($form['card_1'][FIELD_LANGUAGE_NONE]['value'][1]), t('Option 1 does not exist'));
-    $this->assertTrue(empty($form['card_1'][FIELD_LANGUAGE_NONE]['value'][2]), t('Option 2 does not exist'));
-    $this->assertTrue(empty($form['card_1'][FIELD_LANGUAGE_NONE]['value'][3]), t('Option 3 does not exist'));
-    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE]['value'][10]), t('Option 10 exists'));
-    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE]['value'][20]), t('Option 20 exists'));
+    $this->assertTrue(empty($form['card_1'][FIELD_LANGUAGE_NONE][1]), t('Option 1 does not exist'));
+    $this->assertTrue(empty($form['card_1'][FIELD_LANGUAGE_NONE][2]), t('Option 2 does not exist'));
+    $this->assertTrue(empty($form['card_1'][FIELD_LANGUAGE_NONE][3]), t('Option 3 does not exist'));
+    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE][10]), t('Option 10 exists'));
+    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE][20]), t('Option 20 exists'));
 
     // Options are reset when a new field with the same name is created.
     field_delete_field($this->card_1['field_name']);
@@ -81,9 +81,9 @@ class ListFieldTestCase extends DrupalWe
     $this->instance_1 = field_create_instance($this->instance_1);
     $entity = field_test_create_stub_entity(0, 0, FIELD_TEST_BUNDLE);
     $form = drupal_get_form('field_test_entity_form', $entity);
-    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE]['value'][1]), t('Option 1 exists'));
-    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE]['value'][2]), t('Option 2 exists'));
-    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE]['value'][3]), t('Option 3 exists'));
+    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE][1]), t('Option 1 exists'));
+    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE][2]), t('Option 2 exists'));
+    $this->assertTrue(!empty($form['card_1'][FIELD_LANGUAGE_NONE][3]), t('Option 3 exists'));
   }
 }
 
Index: modules/field/modules/options/options.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/options/options.module,v
retrieving revision 1.17
diff -u -p -r1.17 options.module
--- modules/field/modules/options/options.module	10 Nov 2009 17:27:53 -0000	1.17
+++ modules/field/modules/options/options.module	18 Nov 2009 01:36:19 -0000
@@ -11,36 +11,16 @@
  */
 function options_theme() {
   return array(
-    'options_select' => array(
-      'render element' => 'element',
-    ),
-    'options_buttons' => array(
-      'render element' => 'element',
-    ),
-    'options_onoff' => array(
-      'render element' => 'element',
-    ),
     'options_none' => array(
       'variables' => array('instance' => NULL),
-      ),
+    ),
   );
 }
 
 /**
  * Implement hook_field_widget_info().
- *
- * We need custom handling of multiple values because we need
- * to combine them into a options list rather than display
- * cardinality elements. We will use the field module's default
- * handling for default values.
- *
- * Callbacks can be omitted if default handing is used.
- * They're included here just so this module can be used
- * as an example for custom modules that might do things
- * differently.
  */
 function options_field_widget_info() {
-
   return array(
     'options_select' => array(
       'label' => t('Select list'),
@@ -67,290 +47,178 @@ function options_field_widget_info() {
 }
 
 /**
- * Implement hook_element_info().
- */
-function options_element_info() {
-  $types['options_select'] = array(
-    '#input' => TRUE,
-    '#columns' => array('value'),
-    '#delta' => 0,
-    '#process' => array('options_select_elements_process'),
-  );
-  $types['options_buttons'] = array(
-    '#input' => TRUE,
-    '#columns' => array('value'),
-    '#delta' => 0,
-    '#process' => array('options_buttons_elements_process'),
-  );
-  $types['options_onoff'] = array(
-    '#input' => TRUE,
-    '#columns' => array('value'),
-    '#delta' => 0,
-    '#process' => array('options_onoff_elements_process'),
-  );
-  return $types;
-}
-
-/**
  * Implement hook_field_widget().
  */
 function options_field_widget(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
-  $element += array(
-    '#type' => $instance['widget']['type'],
-    '#default_value' => !empty($items) ? $items : array(),
-  );
-  return $element;
-}
+  // Abstract over the actual field columns, to allow different field types to
+  // reuse those widgets.
+  $value_key = key($field['columns']);
+  $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
+  // Form API 'checkboxes' do not suport 0 as an option, so we replace it with
+  // a placeholder within the form workflow.
+  $zero_placeholder = $instance['widget']['type'] == 'options_buttons' && $multiple;
+  // Collect available options for the field.
+  $options = options_get_options($field, $instance, $zero_placeholder);
+  // Put current field values in shape.
+  $default_value = _options_storage_to_form($items, $options, $value_key, $zero_placeholder);
 
-/**
- * Implement hook_field_widget_error().
- */
-function options_field_widget_error($element, $error) {
-  $field_key  = $element['#columns'][0];
-  form_error($element[$field_key], $error['message']);
-}
+  switch ($instance['widget']['type']) {
+    case 'options_select':
+      $element += array(
+        '#type' => 'select',
+        '#default_value' => $default_value,
+        // Do not display a 'multiple' select box if there is only one option.
+        '#multiple' => $multiple && count($options) > 1,
+        '#options' => $options,
+        '#value_key' => $value_key,
+        '#element_validate' => array('options_field_widget_validate'),
+      );
+      break;
 
-/**
- * Process an individual element.
- *
- * Build the form element. When creating a form using FAPI #process,
- * note that $element['#value'] is already set.
- *
- * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
- */
-function options_buttons_elements_process($element, &$form_state, $form) {
-  $field = $form['#fields'][$element['#field_name']]['field'];
-  $instance = $form['#fields'][$element['#field_name']]['instance'];
-  $field_key  = $element['#columns'][0];
-
-  // See if this element is in the database format or the transformed format,
-  // and transform it if necessary.
-  if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) {
-    $element['#value'] = options_data2form($element, $element['#default_value'], $field);
-  }
-  $options = options_options($field, $instance);
-  $required = isset($element['#required']) ? $element['#required'] : $instance['required'];
-  $multiple = isset($element['#multiple']) ? $element['#multiple'] : $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED;
-
-  // Incoming #value is an array (checkboxes) or integer (radios).
-  $keys = $element['#value'][$field_key];
-  if (!is_array($keys)) {
-    $keys = array($keys);
-  }
-
-  // Multiple (checkboxes) need #default_value to be an array, and
-  // non-multiple (radios) need a single default value. If #value is
-  // empty we loop won't run, so initialize $value to the right type.
-  $value = $multiple ? array() : '';
-  foreach ($keys as $key) {
-    if ($multiple) {
-      $value[] = $key;
-    }
-    else {
-      $value = $key;
+    case 'options_buttons':
+      $type = $multiple ? 'checkboxes' : 'radios';
+      // If required and there is one single option, preselect it.
+      if ($element['#required'] && count($options) == 1) {
+        $default_value = array(key($options));
+      }
+      $element += array(
+        '#type' => $type,
+        // Radio buttons need a scalar value.
+        '#default_value' => ($type == 'radios') ? reset($default_value) : $default_value,
+        '#options' => $options,
+        '#zero_placeholder' => $zero_placeholder,
+        '#value_key' => $value_key,
+        '#element_validate' => array('options_field_widget_validate'),
+      );
       break;
-    }
-  }
 
-  // If required and there is one option, make it the default.
-  if ($required && count($options) == 1) {
-    $keys = array_keys($options);
-    if ($multiple) {
-      $value = $keys;
-    }
-    else {
-      $value = $keys[0];
-    }
+    case 'options_onoff':
+      $keys = array_keys($options);
+      $off_value = (!empty($keys) && isset($keys[0])) ? $keys[0] : NULL;
+      $on_value = (!empty($keys) && isset($keys[1])) ? $keys[1] : NULL;
+      $element += array(
+        '#type' => 'checkbox',
+        '#title' => isset($options[$on_value]) ? $options[$on_value] : '',
+        '#default_value' => (isset($default_value[0]) && $default_value[0] == $on_value) ? 1 : 0,
+        '#on_value' => $on_value,
+        '#off_value' => $off_value,
+        '#value_key' => $value_key,
+        '#element_validate' => array('options_field_widget_validate'),
+      );
+      break;
   }
 
-  $element[$field_key] = array(
-    '#type' => $multiple ? 'checkboxes' : 'radios',
-    '#title' => $element['#title'],
-    '#description' => $element['#description'],
-    '#required' => $required,
-    '#multiple' => $multiple,
-    '#options' => $options,
-    '#default_value' => $value,
-  );
-
-  // Set #element_validate in a way that it will not wipe out other
-  // validation functions already set by other modules.
-  if (empty($element['#element_validate'])) {
-    $element['#element_validate'] = array();
-  }
-  array_unshift($element['#element_validate'], 'options_validate');
-
-  // Make sure field info will be available to the validator which
-  // does not get the values in $form.
-  $form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']];
   return $element;
 }
 
 /**
- * Process an individual element.
- *
- * Build the form element. When creating a form using FAPI #process,
- * note that $element['#value'] is already set.
- *
- * The $field and $instance arrays are in $form['#fields'][$element['#field_name']].
+ * Form element validation handler for options element.
  */
-function options_select_elements_process($element, &$form_state, $form) {
-  $field = $form['#fields'][$element['#field_name']]['field'];
-  $instance = $form['#fields'][$element['#field_name']]['instance'];
-  $field_key  = $element['#columns'][0];
-
-  // See if this element is in the database format or the transformed format,
-  // and transform it if necessary.
-  if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) {
-    $element['#value'] = options_data2form($element, $element['#default_value'], $field);
-  }
-
-  $options = options_options($field, $instance);
-  $element[$field_key] = array(
-    '#type' => 'select',
-    '#title' => $element['#title'],
-    '#description' => $element['#description'],
-    '#required' => isset($element['#required']) ? $element['#required'] : $instance['required'],
-    '#multiple' => isset($element['#multiple']) ? $element['#multiple'] : $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED,
-    '#options' => $options,
-    '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL,
-  );
-
-  // Set #element_validate in a way that it will not wipe out other
-  // validation functions already set by other modules.
-  if (empty($element['#element_validate'])) {
-    $element['#element_validate'] = array();
-  }
-  array_unshift($element['#element_validate'], 'options_validate');
-
-  // Make sure field info will be available to the validator which
-  // does not get the values in $form.
-  $form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']];
-  return $element;
-}
-
-/**
- * Process an individual element.
- *
- * Build the form element. When creating a form using FAPI #process,
- * note that $element['#value'] is already set.
- */
-function options_onoff_elements_process($element, &$form_state, $form) {
-  $field = $form['#fields'][$element['#field_name']]['field'];
-  $instance = $form['#fields'][$element['#field_name']]['instance'];
-  $field_key  = $element['#columns'][0];
-
-  // See if this element is in the database format or the transformed format,
-  // and transform it if necessary.
-  if (is_array($element['#value']) && !array_key_exists($field_key, $element['#value'])) {
-    $element['#value'] = options_data2form($element, $element['#default_value'], $field);
-  }
-  $options = options_options($field, $instance);
-  $keys = array_keys($options);
-  $on_value = (!empty($keys) && isset($keys[1])) ? $keys[1] : NULL;
-  $element[$field_key] = array(
-    '#type' => 'checkbox',
-    '#title' => isset($options[$on_value]) ? $options[$on_value] : '',
-    '#description' => $element['#description'],
-    '#default_value' => isset($element['#value'][$field_key][0]) ? $element['#value'][$field_key][0] == $on_value : FALSE,
-    '#return_value' => $on_value,
-  );
+function options_field_widget_validate($element, &$form_state) {
+  $field = $form_state['complete form']['#fields'][$element['#field_name']]['field'];
+  $instance = $form_state['complete form']['#fields'][$element['#field_name']]['instance'];
+
+  // Transpose selections from field => delta to delta => field, turning
+  // multiple selected options into multiple parent elements.
+  $items = _options_form_to_storage($element);
+  form_set_value($element, $items, $form_state);
 
-  // Set #element_validate in a way that it will not wipe out other
-  // validation functions already set by other modules.
-  if (empty($element['#element_validate'])) {
-    $element['#element_validate'] = array();
-  }
-  array_unshift($element['#element_validate'], 'options_validate');
-
-  // Make sure field info will be available to the validator which
-  // does not get the values in $form.
-  $form_state['#fields'][$element['#field_name']] = $form['#fields'][$element['#field_name']];
-  return $element;
+  // Check that we don't exceed the allowed number of values.
+  if ($field['cardinality'] >= 2 && $field['cardinality'] != FIELD_CARDINALITY_UNLIMITED) {
+    if (count($items) > $field['cardinality']) {
+      form_error($element, t('%name: this field cannot hold more than @count values.', array('%name' => t($instance['label']), '@count' => $field['cardinality'])));
+    }
+  }
 }
 
 /**
- * FAPI function to validate options element.
+ * Prepares for a field.
  */
-function options_validate($element, &$form_state) {
-  // Transpose selections from field => delta to delta => field,
-  // turning cardinality selected options into cardinality parent elements.
-  // Immediate parent is the delta, need to get back to parent's parent
-  // to create cardinality elements.
-  $field = $form_state['#fields'][$element['#field_name']]['field'];
-  $items = options_form2data($element, $field);
-  form_set_value($element, $items, $form_state);
+function options_get_options($field, $instance, $zero_placeholder) {
+  // Check if there is a module hook for the option values, otherwise try
+  // list_allowed_values() for an options list.
+  $function = $field['module'] . '_allowed_values';
+  $options = function_exists($function) ? $function($field) : (array) list_allowed_values($field);
 
-  // Check we don't exceed the allowed number of values.
-  if ($field['cardinality'] >= 2) {
-    // Filter out 'none' value (if present, will always be in key 0)
-    $field_key = $element['#columns'][0];
-    if (isset($items[0][$field_key]) && $items[0][$field_key] === '') {
-      unset($items[0]);
+  // Substitute the '_0' placeholder.
+  if ($zero_placeholder) {
+    $values = array_keys($options);
+    // Use a strict comparison, because 0 == 'any string'.
+    $index = array_search(0, $values, TRUE);
+    if ($index !== FALSE) {
+      $values[$index] = '_0';
+      $options = array_combine($values, array_values($options));
     }
-    if (count($items) > $field['cardinality']) {
-      $field_key  = $element['#columns'][0];
-      form_error($element[$field_key], t('%name: this field cannot hold more that @count values.', array('%name' => t($field['widget']['label']), '@count' => $field['cardinality'])));
+  }
+
+  // Add an empty choice for
+  // - non required radios
+  // - non required selects
+  if (!$instance['required']) {
+    if (($instance['widget']['type'] == 'options_buttons' && ($field['cardinality'] == 1)) || ($instance['widget']['type'] == 'options_select')) {
+      $options = array('_none' => theme('options_none', array('instance' => $instance))) + $options;
     }
   }
+  return $options;
 }
 
 /**
- * Helper function to transpose the values as stored in the database
- * to the format the widget needs. Can be called anywhere this
- * transformation is needed.
+ * Transforms stored field values into the format the widgets need.
  */
-function options_data2form($element, $items, $field) {
-  $field_key  = $element['#columns'][0];
-  $field = field_info_field($element['#field_name']);
-  $instance = field_info_instance($element['#object_type'], $element['#field_name'], $element['#bundle']);
-  $options = options_options($field, $instance);
-
-  $items_transposed = options_transpose_array_rows_cols($items);
-  $values = (isset($items_transposed[$field_key]) && is_array($items_transposed[$field_key])) ? $items_transposed[$field_key] : array();
-  $keys = array();
-  foreach ($values as $value) {
-    $key = array_search($value, array_keys($options));
-    if (isset($key)) {
-      $keys[] = $value;
+function _options_storage_to_form($items, $options, $column, $zero_placeholder) {
+  $items_transposed = options_array_transpose($items);
+  $values = (isset($items_transposed[$column]) && is_array($items_transposed[$column])) ? $items_transposed[$column] : array();
+
+  // Substitute the '_0' placeholder.
+  if ($zero_placeholder) {
+    $index = array_search('0', $values);
+    if ($index !== FALSE) {
+      $values[$index] = '_0';
     }
   }
-  if ($field['cardinality'] || $element['#type'] == 'options_onoff') {
-    return array($field_key => $keys);
-  }
-  else {
-    return !empty($keys) ? array($field_key => $value) : array();
-  }
+
+  // Discard values that are not in the current list of options.
+  $values = array_values(array_intersect($values, array_keys($options)));
+  return $values;
 }
 
 /**
- * Helper function to transpose the values returned by submitting the widget
- * to the format to be stored in the field. Can be called anywhere this
- * transformation is needed.
+ * Transforms submitted form values into field storage format.
  */
-function options_form2data($element, $field) {
-  $field_key = $element['#columns'][0];
-  $field = field_info_field($element['#field_name']);
-  $instance = field_info_instance($element['#object_type'], $element['#field_name'], $element['#bundle']);
-  $items = (array) $element[$field_key]['#value'];
-  $options = options_options($field, $instance);
-
-  $values = array_values($items);
-
-  if ($element['#type'] == 'options_onoff' && ($values[0] === 0)) {
-    $keys = array_keys($options);
-    $values = array(array_key_exists(0, $keys) ? $keys[0] : NULL);
+function _options_form_to_storage($element) {
+  $values = array_values((array) $element['#value']);
+
+  // On/off checkbox: transform '0 / 1' into the 'on / off' values.
+  if ($element['#type'] == 'checkbox') {
+    $values = array($values[0] ? $element['#on_value'] : $element['#off_value']);
+  }
+
+  // Substitute the '_0' placeholder.
+  if (!empty($element['#zero_placeholder'])) {
+    $index = array_search('_0', $values);
+    if ($index !== FALSE) {
+      $values[$index] = 0;
+    }
+  }
+
+  // Filter out the 'none' option. Use a strict comparison, because
+  // 0 == 'any string'.
+  $index = array_search('_none', $values, TRUE);
+  if ($index !== FALSE) {
+    unset($values[$index]);
   }
 
   if (empty($values)) {
-    $values[] = NULL;
+    // @todo Apparently not needed IRL, but tests break without.
+    $values = array(NULL);
   }
-  $result = options_transpose_array_rows_cols(array($field_key => $values));
+
+  $result = options_array_transpose(array($element['#value_key'] => $values));
   return $result;
 }
 
 /**
- * Manipulate a 2D array to reverse rows and columns.
+ * Manipulates a 2D array to reverse rows and columns.
  *
  * The default data storage for fields is delta first, column names second.
  * This is sometimes inconvenient for field modules, so this function can be
@@ -362,7 +230,7 @@ function options_form2data($element, $fi
  * @return
  *   The transposed array.
  */
-function options_transpose_array_rows_cols($array) {
+function options_array_transpose($array) {
   $result = array();
   if (is_array($array)) {
     foreach ($array as $key1 => $value1) {
@@ -380,24 +248,10 @@ function options_transpose_array_rows_co
 }
 
 /**
- * Helper function for finding the allowed values list for a field.
- *
- * See if there is a module hook for the option values.
- * Otherwise, try list_allowed_values() for an options list.
+ * Implement hook_field_widget_error().
  */
-function options_options($field, $instance) {
-  $function = $field['module'] . '_allowed_values';
-  $options = function_exists($function) ? $function($field) : (array) list_allowed_values($field);
-  // Add an empty choice for :
-  // - non required radios
-  // - non required selects
-  if (!$instance['required']) {
-    if ((in_array($instance['widget']['type'], array('options_buttons', 'node_reference_buttons', 'user_reference_buttons')) && !$field['cardinality'])
-      || (in_array($instance['widget']['type'], array('options_select', 'node_reference_select', 'user_reference_select')))) {
-      $options = array('' => theme('options_none', array('instance' => $instance))) + $options;
-    }
-  }
-  return $options;
+function options_field_widget_error($element, $error) {
+  form_error($element, $error['message']);
 }
 
 /**
@@ -408,40 +262,10 @@ function theme_options_none($variables) 
   $instance = $variables['instance'];
   switch ($instance['widget']['type']) {
     case 'options_buttons':
-    case 'node_reference_buttons':
-    case 'user_reference_buttons':
       return t('N/A');
     case 'options_select':
-    case 'node_reference_select':
-    case 'user_reference_select':
       return t('- None -');
     default :
       return '';
   }
 }
-
-/**
- * FAPI themes for options.
- *
- * The select, checkboxes or radios are already rendered by the
- * select, checkboxes, or radios themes and the HTML output
- * lives in $variables['element']['#children']. Override this theme to
- * make custom changes to the output.
- *
- * $variables['element']['#field_name'] contains the field name
- * $variables['element']['#delta] is the position of this element in the group
- */
-function theme_options_select($variables) {
-  $element = $variables['element'];
-  return $element['#children'];
-}
-
-function theme_options_onoff($variables) {
-  $element = $variables['element'];
-  return $element['#children'];
-}
-
-function theme_options_buttons($variables) {
-  $element = $variables['element'];
-  return $element['#children'];
-}
\ No newline at end of file
Index: modules/field/modules/options/options.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/field/modules/options/options.test,v
retrieving revision 1.3
diff -u -p -r1.3 options.test
--- modules/field/modules/options/options.test	10 Nov 2009 17:27:53 -0000	1.3
+++ modules/field/modules/options/options.test	17 Nov 2009 17:28:59 -0000
@@ -13,96 +13,52 @@ class OptionsWidgetsTestCase extends Dru
   function setUp() {
     parent::setUp('field_test');
 
+    // Field with cardinality 1.
     $this->card_1 = array(
       'field_name' => 'card_1',
       'type' => 'list',
       'cardinality' => 1,
       'settings' => array(
-        'allowed_values' => "1|One\n2|Two\n3|Three\n",
+        // Make sure that 0 works as an option.
+        'allowed_values' => "0|Zero\n1|One\n2|Two\n",
       ),
     );
     $this->card_1 = field_create_field($this->card_1);
 
+    // Field with cardinality 2.
     $this->card_2 = array(
       'field_name' => 'card_2',
       'type' => 'list',
       'cardinality' => 2,
       'settings' => array(
-        'allowed_values' => "1|One\n2|Two\n3|Three\n",
+        // Make sure that 0 works as an option.
+        'allowed_values' => "0|Zero\n1|One\n2|Two\n",
       ),
     );
     $this->card_2 = field_create_field($this->card_2);
-  }
-
-  /**
-   * Test widgets
-   */
 
-  /**
-   * Return an element from rendered HTML by id, or '' if id is not found.
-   */
-  function getTagById($html, $id) {
-    // @todo: ids sometimes have an extra -n after them; why?
-    if (preg_match('@(<[^>]*id="' . $id . '(?:-\d+)?"[^>]*/>)@i', $html, $m)) {
-      return $m[0];
-    }
-    return '';
-  }
-
-  /**
-   * Assert that a checkbox identified by $id is checked.
-   */
-  function assertIsChecked($html, $id) {
-    $input = $this->getTagById($html, $id);
-    $this->assertTrue(preg_match('@checked="checked"@', $input), t('Checkbox %id is checked', array('%id' => $id)));
-  }
-
-  /**
-   * Assert that a checkbox identified by $id is found but is not checked.
-   */
-  function assertIsNotChecked($html, $id) {
-    $input = $this->getTagById($html, $id);
-    if (!empty($input)) {
-      $this->assertFalse(preg_match('@checked@', $input), t('Checkbox %id is not checked', array('%id' => $id)));
-    }
-    else {
-      $this->fail(t('Checkbox %id is not found', array('%id' => $id)));
-    }
-  }
-
-  /**
-   * Return an <option> element by value from rendered HTML, or
-   * '' if it is not found.
-   */
-  function getOptionByValue($html, $value) {
-    if (preg_match('@(<option[^>]*value="' . $value . '"[^>]*>[^<]*</option>)@i', $html, $m)) {
-      return $m[0];
-    }
-    return '';
-  }
+    // Boolean field.
+    $this->bool = array(
+      'field_name' => 'bool',
+      'type' => 'list_boolean',
+      'cardinality' => 1,
+      'settings' => array(
+        // Make sure that 0 works as a 'on' value'.
+        'allowed_values' => "1|No\n0|Yes\n",
+      ),
+    );
+    $this->bool = field_create_field($this->bool);
 
-  /**
-   * Assert that an <option> for value $value is selected.
-   */
-  function assertIsSelected($html, $value) {
-    $input = $this->getOptionByValue($html, $value);
-    $this->assertTrue(preg_match('@selected="selected"@', $input), t('Option %value is selected', array('%value' => $value)));
+    // Create a web user.
+    $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content'));
+    $this->drupalLogin($this->web_user);
   }
 
   /**
-   * Assert that an <option> for value $value is found but is not selected.
+   * Tests the 'options_buttons' widget (single select).
    */
-  function assertIsNotSelected($html, $value) {
-    $input = $this->getOptionByValue($html, $value);
-    if (!empty($input)) {
-      $this->assertFalse(preg_match('@selected@', $input), t('Option %value is not selected', array('%value' => $value)));
-    }
-    else {
-      $this->fail(t('Option %value is not found', array('%value' => $value)));
-    }
-  }
-
   function testRadioButtons() {
+    // Create an instance of the 'single value' field.
     $instance = array(
       'field_name' => $this->card_1['field_name'],
       'object_type' => 'test_entity',
@@ -112,35 +68,53 @@ class OptionsWidgetsTestCase extends Dru
       ),
     );
     $instance = field_create_instance($instance);
+    $langcode = FIELD_LANGUAGE_NONE;
 
-    $entity = field_test_create_stub_entity(0, 0, FIELD_TEST_BUNDLE);
+    // Create an entity.
+    $entity_init = field_test_create_stub_entity();
+    $entity = clone $entity_init;
+    $entity->is_new = TRUE;
+    field_test_entity_save($entity);
 
     // With no field data, no buttons are checked.
-    $form = drupal_get_form('field_test_entity_form', $entity);
-    $render = drupal_render($form);
-    $this->assertIsNotChecked($render, 'edit-card-1-zxx-value-1');
-    $this->assertIsNotChecked($render, 'edit-card-1-zxx-value-2');
-    $this->assertIsNotChecked($render, 'edit-card-1-zxx-value-3');
-
-    // With field data, the selected button is checked.
-    $entity->card_1[FIELD_LANGUAGE_NONE][0]['value'] = '2';
-    $form = drupal_get_form('field_test_entity_form', $entity);
-    $render = drupal_render($form);
-    $this->assertIsNotChecked($render, 'edit-card-1-zxx-value-1');
-    $this->assertIsChecked($render, 'edit-card-1-zxx-value-2');
-    $this->assertIsNotChecked($render, 'edit-card-1-zxx-value-3');
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertNoFieldChecked("edit-card-1-$langcode-0");
+    $this->assertNoFieldChecked("edit-card-1-$langcode-1");
+    $this->assertNoFieldChecked("edit-card-1-$langcode-2");
+
+    // Select first option.
+    $edit = array("card_1[$langcode]" => 0);
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertListValues($entity_init, 'card_1', $langcode, array(0));
+
+    // Check that the selected button is checked.
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertFieldChecked("edit-card-1-$langcode-0");
+    $this->assertNoFieldChecked("edit-card-1-$langcode-1");
+    $this->assertNoFieldChecked("edit-card-1-$langcode-2");
+
+    // Unselect option.
+    $edit = array("card_1[$langcode]" => '_none');
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertListValues($entity_init, 'card_1', $langcode, array());
 
     // Required radios with one option is auto-selected.
+    $this->card_1['settings']['allowed_values'] = '99|Only allowed value';
+    field_update_field($this->card_1);
     $instance['required'] = TRUE;
     field_update_instance($instance);
-    $this->card_1['settings']['allowed_values'] = '99|Only allowed value for radios';
-    field_update_field($this->card_1);
-    $form = drupal_get_form('field_test_entity_form', $entity);
-    $render = drupal_render($form);
-    $this->assertIsChecked($render, 'edit-card-1-zxx-value-99');
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertFieldChecked("edit-card-1-$langcode-99");
   }
 
+  /**
+   * Tests the 'options_buttons' widget (multiple select).
+   */
   function testCheckBoxes() {
+    // Checkboxes do not support '0' as an option, the widget internally
+    // replaces it with '_0'.
+
+    // Create an instance of the 'multiple values' field.
     $instance = array(
       'field_name' => $this->card_2['field_name'],
       'object_type' => 'test_entity',
@@ -150,39 +124,85 @@ class OptionsWidgetsTestCase extends Dru
       ),
     );
     $instance = field_create_instance($instance);
+    $langcode = FIELD_LANGUAGE_NONE;
+
+    // Create an entity.
+    $entity_init = field_test_create_stub_entity();
+    $entity = clone $entity_init;
+    $entity->is_new = TRUE;
+    field_test_entity_save($entity);
+
+    // Display form: with no field data, nothing is checked.
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertNoFieldChecked("edit-card-2-$langcode--0");
+    $this->assertNoFieldChecked("edit-card-2-$langcode-1");
+    $this->assertNoFieldChecked("edit-card-2-$langcode-2");
+
+    // Submit form: select first and third options.
+    $edit = array(
+      "card_2[$langcode][_0]" => TRUE,
+      "card_2[$langcode][1]" => FALSE,
+      "card_2[$langcode][2]" => TRUE,
+    );
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertListValues($entity_init, 'card_2', $langcode, array(0, 2));
+
+    // Display form: check that the right options are selected.
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertFieldChecked("edit-card-2-$langcode--0");
+    $this->assertNoFieldChecked("edit-card-2-$langcode-1");
+    $this->assertFieldChecked("edit-card-2-$langcode-2");
+
+    // Submit form: select only first option.
+    $edit = array(
+      "card_2[$langcode][_0]" => TRUE,
+      "card_2[$langcode][1]" => FALSE,
+      "card_2[$langcode][2]" => FALSE,
+    );
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertListValues($entity_init, 'card_2', $langcode, array(0));
 
-    $entity = field_test_create_stub_entity(0, 0, FIELD_TEST_BUNDLE);
+    // Display form: check that the right options are selected.
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertFieldChecked("edit-card-2-$langcode--0");
+    $this->assertNoFieldChecked("edit-card-2-$langcode-1");
+    $this->assertNoFieldChecked("edit-card-2-$langcode-2");
+
+    // Submit form: select the three options while the field accepts only 2.
+    $edit = array(
+      "card_2[$langcode][_0]" => TRUE,
+      "card_2[$langcode][1]" => TRUE,
+      "card_2[$langcode][2]" => TRUE,
+    );
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertText('this field cannot hold more than 2 values', t('Validation error was displayed.'));
 
-    // With no field data, nothing is checked.
-    $form = drupal_get_form('field_test_entity_form', $entity);
-    $render = drupal_render($form);
-    $this->assertIsNotChecked($render, 'edit-card-2-zxx-value-1');
-    $this->assertIsNotChecked($render, 'edit-card-2-zxx-value-2');
-    $this->assertIsNotChecked($render, 'edit-card-2-zxx-value-3');
-
-    // With field data, the specified items are checked.
-    $entity->card_2[FIELD_LANGUAGE_NONE][0]['value'] = '2';
-    $entity->card_2[FIELD_LANGUAGE_NONE][1]['value'] = '3';
-    $form = drupal_get_form('field_test_entity_form', $entity);
-    $render = drupal_render($form);
-    $this->assertIsNotChecked($render, 'edit-card-2-zxx-value-1');
-    $this->assertIsChecked($render, 'edit-card-2-zxx-value-2');
-    $this->assertIsChecked($render, 'edit-card-2-zxx-value-3');
+    // Submit form: uncheck all options.
+    $edit = array(
+      "card_2[$langcode][_0]" => FALSE,
+      "card_2[$langcode][1]" => FALSE,
+      "card_2[$langcode][2]" => FALSE,
+    );
+    $this->drupalPost(NULL, $edit, t('Save'));
+    // Check that the value was saved.
+    $this->assertListValues($entity_init, 'card_2', $langcode, array());
 
     // Required checkbox with one option is auto-selected.
+    $this->card_2['settings']['allowed_values'] = '99|Only allowed value';
+    field_update_field($this->card_2);
     $instance['required'] = TRUE;
     field_update_instance($instance);
-    $this->card_2['settings']['allowed_values'] = '99|Only allowed value for checkboxes';
-    field_update_field($this->card_2);
-    unset($entity->card_2);
-    $form = drupal_get_form('field_test_entity_form', $entity);
-    $render = drupal_render($form);
-    $this->assertIsChecked($render, 'edit-card-2-zxx-value-99');
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertFieldChecked("edit-card-2-$langcode-99");
   }
 
-  function testSelectList() {
+  /**
+   * Tests the 'options_select' widget (single select).
+   */
+  function testSelectListSingle() {
+    // Create an instance of the 'single value' field.
     $instance = array(
-      'field_name' => $this->card_2['field_name'],
+      'field_name' => $this->card_1['field_name'],
       'object_type' => 'test_entity',
       'bundle' => FIELD_TEST_BUNDLE,
       'widget' => array(
@@ -190,41 +210,194 @@ class OptionsWidgetsTestCase extends Dru
       ),
     );
     $instance = field_create_instance($instance);
+    $langcode = FIELD_LANGUAGE_NONE;
 
-    $entity = field_test_create_stub_entity(0, 0, FIELD_TEST_BUNDLE);
+    // Create an entity.
+    $entity_init = field_test_create_stub_entity();
+    $entity = clone $entity_init;
+    $entity->is_new = TRUE;
+    field_test_entity_save($entity);
+
+    // Display form: with no field data, nothing is selected.
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertNoOptionSelected("edit-card-1-$langcode", 0);
+    $this->assertNoOptionSelected("edit-card-1-$langcode", 1);
+    $this->assertNoOptionSelected("edit-card-1-$langcode", 2);
+
+    // Submit form: select first option.
+    $edit = array("card_1[$langcode]" => 0);
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertListValues($entity_init, 'card_1', $langcode, array(0));
+
+    // Display form: check that the right options are selected.
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertOptionSelected("edit-card-1-$langcode", 0);
+    $this->assertNoOptionSelected("edit-card-1-$langcode", 1);
+    $this->assertNoOptionSelected("edit-card-1-$langcode", 2);
+
+    // Submit form: Unselect the option.
+    $edit = array("card_1[$langcode]" => '_none');
+    $this->drupalPost('test-entity/' . $entity->ftid .'/edit', $edit, t('Save'));
+    $this->assertListValues($entity_init, 'card_1', $langcode, array());
 
-    // With no field data, no options are selected.
-    $form = drupal_get_form('field_test_entity_form', $entity);
-    $render = drupal_render($form);
-    $this->assertIsNotSelected($render, 1);
-    $this->assertIsNotSelected($render, 2);
-    $this->assertIsNotSelected($render, 3);
-
-    // With field data, the specified options are selected.
-    $entity->card_2[FIELD_LANGUAGE_NONE][0]['value'] = '2';
-    $entity->card_2[FIELD_LANGUAGE_NONE][1]['value'] = '1';
-    $form = drupal_get_form('field_test_entity_form', $entity);
-    $render = drupal_render($form);
-    $this->assertIsSelected($render, 1);
-    $this->assertIsSelected($render, 2);
-    $this->assertIsNotSelected($render, 3);
-
-    // A non-required select list has an empty key.
-    $form = drupal_get_form('field_test_entity_form', $entity);
-    $this->assertTrue(isset($form['card_2'][FIELD_LANGUAGE_NONE]['value']['#options']['']), 'A non-required select list has an empty key.');
+    // A required select list does not have an empty key.
+    $instance['required'] = TRUE;
+    field_update_instance($instance);
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertFalse($this->xpath('//select[@id="edit-card-1-' . $langcode . '"]//option[@value=""]'), t('A required select list does not have an empty key.'));
+
+    // We do not have to test that a required select list with one option is
+    // auto-selected because the browser does it for us.
+  }
+
+  /**
+   * Tests the 'options_select' widget (multiple select).
+   */
+  function testSelectListMultiple() {
+    // Create an instance of the 'multiple values' field.
+    $instance = array(
+      'field_name' => $this->card_2['field_name'],
+      'object_type' => 'test_entity',
+      'bundle' => FIELD_TEST_BUNDLE,
+      'widget' => array(
+        'type' => 'options_select',
+      ),
+    );
+    $instance = field_create_instance($instance);
+    $langcode = FIELD_LANGUAGE_NONE;
+
+    // Create an entity.
+    $entity_init = field_test_create_stub_entity();
+    $entity = clone $entity_init;
+    $entity->is_new = TRUE;
+    field_test_entity_save($entity);
+
+    // Display form: with no field data, nothing is selected.
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertNoOptionSelected("edit-card-2-$langcode", 0);
+    $this->assertNoOptionSelected("edit-card-2-$langcode", 1);
+    $this->assertNoOptionSelected("edit-card-2-$langcode", 2);
+
+    // Submit form: select first and third options.
+    $edit = array("card_2[$langcode][]" => array(0 => 0, 2 => 2));
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertListValues($entity_init, 'card_2', $langcode, array(0, 2));
+
+    // Display form: check that the right options are selected.
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertOptionSelected("edit-card-2-$langcode", 0);
+    $this->assertNoOptionSelected("edit-card-2-$langcode", 1);
+    $this->assertOptionSelected("edit-card-2-$langcode", 2);
+
+    // Submit form: select only first option.
+    $edit = array("card_2[$langcode][]" => array(0 => 0));
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertListValues($entity_init, 'card_2', $langcode, array(0));
+
+    // Display form: check that the right options are selected.
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertOptionSelected("edit-card-2-$langcode", 0);
+    $this->assertNoOptionSelected("edit-card-2-$langcode", 1);
+    $this->assertNoOptionSelected("edit-card-2-$langcode", 2);
+
+    // Submit form: select the three options while the field accepts only 2.
+    $edit = array("card_2[$langcode][]" => array(0 => 0, 1 => 1, 2 => 2));
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertText('this field cannot hold more than 2 values', t('Validation error was displayed.'));
+
+    // Submit form: uncheck all options.
+    $edit = array("card_2[$langcode][]" => array());
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertListValues($entity_init, 'card_2', $langcode, array());
+
+    // Test the 'None' option.
+
+    // Check that the 'none' option has no efect if actual options are selected
+    // as well.
+    $edit = array("card_2[$langcode][]" => array('_none' => '_none', 0 => 0));
+    $this->drupalPost('test-entity/' . $entity->ftid .'/edit', $edit, t('Save'));
+    $this->assertListValues($entity_init, 'card_2', $langcode, array(0));
+
+    // Check that selecting the 'none' option empties the field.
+    $edit = array("card_2[$langcode][]" => array('_none' => '_none'));
+    $this->drupalPost('test-entity/' . $entity->ftid .'/edit', $edit, t('Save'));
+    $this->assertListValues($entity_init, 'card_2', $langcode, array());
 
     // A required select list does not have an empty key.
     $instance['required'] = TRUE;
     field_update_instance($instance);
-    $form = drupal_get_form('field_test_entity_form', $entity);
-    $this->assertTrue(!isset($form['card_2'][FIELD_LANGUAGE_NONE]['value']['#options']['']), 'A required select list does not have an empty key.');
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertFalse($this->xpath('//select[@id="edit-card-2-' . $langcode . '"]//option[@value=""]'), t('A required select list does not have an empty key.'));
+
+    // We do not have to test that a required select list with one option is
+    // auto-selected because the browser does it for us.
+  }
+
+  /**
+   * Tests the 'options_onoff' widget.
+   */
+  function testOnOffCheckbox() {
+    // Create an instance of the 'boolean' field.
+    $instance = array(
+      'field_name' => $this->bool['field_name'],
+      'object_type' => 'test_entity',
+      'bundle' => FIELD_TEST_BUNDLE,
+      'widget' => array(
+        'type' => 'options_onoff',
+      ),
+    );
+    $instance = field_create_instance($instance);
+    $langcode = FIELD_LANGUAGE_NONE;
 
-    // We don't have to test that a required select list with one
-    // option is auto-selected because the browser does it for us.
+    // Create an entity.
+    $entity_init = field_test_create_stub_entity();
+    $entity = clone $entity_init;
+    $entity->is_new = TRUE;
+    field_test_entity_save($entity);
+
+    // Display form: with no field data, option is unchecked.
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertNoFieldChecked("edit-bool-$langcode");
+
+    // Submit form: check the option.
+    $edit = array("bool[$langcode]" => TRUE);
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertListValues($entity_init, 'bool', $langcode, array(0));
+
+    // Display form: check that the right options are selected.
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertFieldChecked("edit-bool-$langcode");
+
+    // Submit form: uncheck the option.
+    $edit = array("bool[$langcode]" => FALSE);
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertListValues($entity_init, 'bool', $langcode, array(1));
+
+    // Display form: with 'off' value, option is unchecked.
+    $this->drupalGet('test-entity/' . $entity->ftid .'/edit');
+    $this->assertNoFieldChecked("edit-bool-$langcode");
   }
 
   /**
-   * @todo: Test formatters.
+   * Assert that a 'list' field has the expected values in an entity.
+   *
+   * @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.
    */
+  function assertListValues($entity, $field_name, $langcode, $expected_values) {
+    $e = clone $entity;
+    field_attach_load('test_entity', array($e->ftid => $e));
+    $values = isset($e->{$field_name}[$langcode]) ? $e->{$field_name}[$langcode] : array();
+    $this->assertEqual(count($values), count($expected_values), t('Expected number of values were saved.'));
+    foreach ($expected_values as $key => $value) {
+      $this->assertEqual($values[$key]['value'], $value, t('Option @value was saved correctly.', array('@value' => $value)));
+    }
+  }
 }
 
Index: modules/forum/forum.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/forum/forum.test,v
retrieving revision 1.40
diff -u -p -r1.40 forum.test
--- modules/forum/forum.test	12 Nov 2009 20:30:22 -0000	1.40
+++ modules/forum/forum.test	18 Nov 2009 02:14:46 -0000
@@ -267,7 +267,7 @@ class ForumTestCase extends DrupalWebTes
     $edit = array(
       "title[$langcode][0][value]" => $title,
       "body[$langcode][0][value]" => $body,
-      "taxonomy_forums[$langcode][value]" => $tid,
+      "taxonomy_forums[$langcode]" => $tid,
     );
 
     // TODO The taxonomy select value is set by drupal code when the tid is part
@@ -357,7 +357,7 @@ class ForumTestCase extends DrupalWebTes
       $edit["title[$langcode][0][value]"] = 'node/' . $node->nid;
       $edit["body[$langcode][0][value]"] = $this->randomName(256);
       // Assume the topic is initially associated with $forum.
-      $edit["taxonomy_forums[$langcode][value]"] = $this->root_forum['tid'];
+      $edit["taxonomy_forums[$langcode]"] = $this->root_forum['tid'];
       $edit['shadow'] = TRUE;
       $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
       $this->assertRaw(t('Forum topic %title has been updated.', array('%title' => $edit["title[$langcode][0][value]"])), t('Forum node was edited'));
Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.171
diff -u -p -r1.171 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	18 Nov 2009 04:56:17 -0000	1.171
+++ modules/simpletest/drupal_web_test_case.php	18 Nov 2009 11:56:50 -0000
@@ -1656,22 +1656,37 @@ class DrupalWebTestCase extends DrupalTe
             break;
           case 'select':
             $new_value = $edit[$name];
-            $index = 0;
-            $key = preg_replace('/\[\]$/', '', $name);
             $options = $this->getAllOptions($element);
-            foreach ($options as $option) {
-              if (is_array($new_value)) {
-                $option_value= (string)$option['value'];
-                if (in_array($option_value, $new_value)) {
-                  $post[$key . '[' . $index++ . ']'] = $option_value;
-                  $done = TRUE;
-                  unset($edit[$name]);
+            if (is_array($new_value)) {
+              // Multiple select box.
+              if (!empty($new_value)) {
+                $index = 0;
+                $key = preg_replace('/\[\]$/', '', $name);
+                foreach ($options as $option) {
+                  $option_value = (string)$option['value'];
+                  if (in_array($option_value, $new_value)) {
+                    $post[$key . '[' . $index++ . ']'] = $option_value;
+                    $done = TRUE;
+                    unset($edit[$name]);
+                  }
                 }
               }
-              elseif ($new_value == $option['value']) {
-                $post[$name] = $new_value;
-                unset($edit[$name]);
+              else {
+                // No options selected: do not include any POST data for the
+                // element.
                 $done = TRUE;
+                unset($edit[$name]);
+              }
+            }
+            else {
+              // Single select box.
+              foreach ($options as $option) {
+                if ($new_value == $option['value']) {
+                  $post[$name] = $new_value;
+                  unset($edit[$name]);
+                  $done = TRUE;
+                  break;
+                }
               }
             }
             break;
@@ -2507,6 +2522,40 @@ class DrupalWebTestCase extends DrupalTe
   }
 
   /**
+   * Assert that a select option in the current page is not checked.
+   *
+   * @param $id
+   *   Id of select field to assert.
+   * @param $option
+   *   Option to assert.
+   * @param $message
+   *   Message to display.
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertOptionSelected($id, $option, $message = '') {
+    $elements = $this->xpath('//select[@id="' . $id . '"]//option[@value="' . $option . '"]');
+    return $this->assertTrue(isset($elements[0]) && !empty($elements[0]['selected']), $message ? $message : t('Option @option for field @id is selected.', array('@option' => $option, '@id' => $id)), t('Browser'));
+  }
+
+  /**
+   * Assert that a select option in the current page is not checked.
+   *
+   * @param $id
+   *   Id of select field to assert.
+   * @param $option
+   *   Option to assert.
+   * @param $message
+   *   Message to display.
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertNoOptionSelected($id, $option, $message = '') {
+    $elements = $this->xpath('//select[@id="' . $id . '"]//option[@value="' . $option . '"]');
+    return $this->assertTrue(isset($elements[0]) && empty($elements[0]['selected']), $message ? $message : t('Option @option for field @id is not selected.', array('@option' => $option, '@id' => $id)), t('Browser'));
+  }
+
+  /**
    * Assert that a field exists with the given name or id.
    *
    * @param $field
Index: modules/taxonomy/taxonomy.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v
retrieving revision 1.538
diff -u -p -r1.538 taxonomy.module
--- modules/taxonomy/taxonomy.module	13 Nov 2009 09:24:06 -0000	1.538
+++ modules/taxonomy/taxonomy.module	18 Nov 2009 01:35:47 -0000
@@ -1258,7 +1258,7 @@ function taxonomy_autocomplete_validate(
       }
       $values[] = $term->tid;
     }
-    $value = options_transpose_array_rows_cols(array('value' => $values));
+    $value = options_array_transpose(array('value' => $values));
   }
   else {
     $value = array();
Index: modules/taxonomy/taxonomy.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.test,v
retrieving revision 1.58
diff -u -p -r1.58 taxonomy.test
--- modules/taxonomy/taxonomy.test	11 Nov 2009 17:10:49 -0000	1.58
+++ modules/taxonomy/taxonomy.test	17 Nov 2009 16:18:27 -0000
@@ -381,7 +381,7 @@ class TaxonomyTermTestCase extends Taxon
     $langcode = FIELD_LANGUAGE_NONE;
     $edit["title[$langcode][0][value]"] = $this->randomName();
     $edit["body[$langcode][0][value]"] = $this->randomName();
-    $edit[$this->instance['field_name'] . '[' . $langcode .'][value][]'] = $term1->tid;
+    $edit[$this->instance['field_name'] . '[' . $langcode .'][]'] = $term1->tid;
     $this->drupalPost('node/add/article', $edit, t('Save'));
 
     // Check that the term is displayed when the node is viewed.
@@ -390,7 +390,7 @@ class TaxonomyTermTestCase extends Taxon
     $this->assertText($term1->name, t('Term is displayed when viewing the node.'));
 
     // Edit the node with a different term.
-    $edit[$this->instance['field_name'] . '[' . $langcode . '][value][]'] = $term2->tid;
+    $edit[$this->instance['field_name'] . '[' . $langcode . '][]'] = $term2->tid;
     $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
 
     $this->drupalGet('node/' . $node->nid);
@@ -809,11 +809,11 @@ class TaxonomyTermFieldTestCase extends 
     // Display creation form.
     $langcode = FIELD_LANGUAGE_NONE;
     $this->drupalGet('test-entity/add/test-bundle');
-    $this->assertFieldByName("{$this->field_name}[$langcode][value]", '', t('Widget is displayed'));
+    $this->assertFieldByName("{$this->field_name}[$langcode]", '', t('Widget is displayed'));
 
     // Submit with some value.
     $edit = array(
-      "{$this->field_name}[$langcode][value]" => array($term->tid),
+      "{$this->field_name}[$langcode]" => array($term->tid),
     );
     $this->drupalPost(NULL, $edit, t('Save'));
     preg_match('|test-entity/(\d+)/edit|', $this->url, $match);
