taxonomy.test 62.1 KB
Newer Older
1 2
<?php

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

8
/**
9 10
* Class with common helper methods.
*/
11 12 13 14 15 16 17 18 19 20
class TaxonomyWebTestCase extends DrupalWebTestCase {

  /**
   * Returns a new vocabulary with random properties.
   */
  function createVocabulary() {
    // Create a vocabulary.
    $vocabulary = new stdClass();
    $vocabulary->name = $this->randomName();
    $vocabulary->description = $this->randomName();
21
    $vocabulary->machine_name = drupal_strtolower($this->randomName());
22 23 24 25 26 27 28 29 30 31
    $vocabulary->help = '';
    $vocabulary->nodes = array('article' => 'article');
    $vocabulary->weight = mt_rand(0, 10);
    taxonomy_vocabulary_save($vocabulary);
    return $vocabulary;
  }

  /**
   * Returns a new term with random properties in vocabulary $vid.
   */
32
  function createTerm($vocabulary) {
33 34
    $term = new stdClass();
    $term->name = $this->randomName();
35
    $term->description = $this->randomName();
36 37
    // Use the first available text format.
    $term->format = db_query_range('SELECT format FROM {filter_format}', 0, 1)->fetchField();
38
    $term->vid = $vocabulary->vid;
39 40 41 42 43 44
    taxonomy_term_save($term);
    return $term;
  }
}

/**
45 46
* Tests for the taxonomy vocabulary interface.
*/
47
class TaxonomyVocabularyFunctionalTest extends TaxonomyWebTestCase {
48

49
  public static function getInfo() {
50
    return array(
51 52 53
      'name' => 'Taxonomy vocabulary interface',
      'description' => 'Test the taxonomy vocabulary interface.',
      'group' => 'Taxonomy',
54 55 56 57 58 59
    );
  }

  function setUp() {
    parent::setUp();
    $this->admin_user = $this->drupalCreateUser(array('administer taxonomy'));
60 61
    $this->drupalLogin($this->admin_user);
    $this->vocabulary = $this->createVocabulary();
62 63
  }

64
  /**
65
   * Create, edit and delete a vocabulary via the user interface.
66
   */
67 68
  function testVocabularyInterface() {
    // Visit the main taxonomy administration page.
69
    $this->drupalGet('admin/structure/taxonomy');
70 71 72 73

    // Create a new vocabulary.
    $this->clickLink(t('Add vocabulary'));
    $edit = array();
74
    $machine_name = drupal_strtolower($this->randomName());
75 76
    $edit['name'] = $this->randomName();
    $edit['description'] = $this->randomName();
77
    $edit['machine_name'] = $machine_name;
78
    $this->drupalPost(NULL, $edit, t('Save'));
79
    $this->assertRaw(t('Created new vocabulary %name.', array('%name' => $edit['name'])), t('Vocabulary created successfully'));
80 81

    // Edit the vocabulary.
82
    $this->drupalGet('admin/structure/taxonomy');
83
    $this->assertText($edit['name'], t('Vocabulary found in the vocabulary overview listing.'));
84 85 86 87
    $this->clickLink(t('edit vocabulary'));
    $edit = array();
    $edit['name'] = $this->randomName();
    $this->drupalPost(NULL, $edit, t('Save'));
88
    $this->drupalGet('admin/structure/taxonomy');
89
    $this->assertText($edit['name'], t('Vocabulary found in the vocabulary overview listing.'));
90 91

    // Try to submit a vocabulary with a duplicate machine name.
92
    $edit['machine_name'] = $machine_name;
93
    $this->drupalPost('admin/structure/taxonomy/add', $edit, t('Save'));
94
    $this->assertText(t('The machine-readable name is already in use. It must be unique.'));
95 96 97

    // Try to submit an invalid machine name.
    $edit['machine_name'] = '!&^%';
98
    $this->drupalPost('admin/structure/taxonomy/add', $edit, t('Save'));
99
    $this->assertText(t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
100
  }
101 102 103 104 105 106 107 108 109 110

  /**
   * Changing weights on the vocabulary overview with two or more vocabularies.
   */
  function testTaxonomyAdminChangingWeights() {
    // Create some vocabularies.
    for ($i = 0; $i < 10; $i++) {
      $this->createVocabulary();
    }
    // Get all vocabularies and change their weights.
111
    $vocabularies = taxonomy_vocabulary_load_multiple(FALSE);
112 113 114 115
    $edit = array();
    foreach ($vocabularies as $key => $vocabulary) {
      $vocabulary->weight = -$vocabulary->weight;
      $vocabularies[$key]->weight = $vocabulary->weight;
116
      $edit[$key . '[weight]'] = $vocabulary->weight;
117 118
    }
    // Saving the new weights via the interface.
119
    $this->drupalPost('admin/structure/taxonomy', $edit, t('Save'));
120 121

    // Load the vocabularies from the database.
122
    $new_vocabularies = taxonomy_vocabulary_load_multiple(FALSE);
123 124 125

    // Check that the weights are saved in the database correctly.
    foreach ($vocabularies as $key => $vocabulary) {
126
      $this->assertEqual($new_vocabularies[$key]->weight, $vocabularies[$key]->weight, t('The vocabulary weight was changed.'));
127 128 129 130 131 132 133 134
    }
  }

  /**
   * Test the vocabulary overview with no vocabularies.
   */
  function testTaxonomyAdminNoVocabularies() {
    // Delete all vocabularies.
135
    $vocabularies = taxonomy_vocabulary_load_multiple(FALSE);
136
    foreach ($vocabularies as $key => $vocabulary) {
137
      taxonomy_vocabulary_delete($key);
138 139
    }
    // Confirm that no vocabularies are found in the database.
140
    $this->assertFalse(taxonomy_vocabulary_load_multiple(FALSE), t('No vocabularies found in the database'));
141
    $this->drupalGet('admin/structure/taxonomy');
142
    // Check the default message for no vocabularies.
143
    $this->assertText(t('No vocabularies available.'), t('No vocabularies were found.'));
144 145 146 147 148 149 150 151 152
  }

  /**
   * Deleting a vocabulary.
   */
  function testTaxonomyAdminDeletingVocabulary() {
    // Create a vocabulary.
    $edit = array(
      'name' => $this->randomName(),
153
      'machine_name' => drupal_strtolower($this->randomName()),
154
    );
155
    $this->drupalPost('admin/structure/taxonomy/add', $edit, t('Save'));
156
    $this->assertText(t('Created new vocabulary'), t('New vocabulary was created.'));
157 158

    // Check the created vocabulary.
159
    $vocabularies = taxonomy_vocabulary_load_multiple(FALSE);
160
    $vid = $vocabularies[count($vocabularies)-1]->vid;
161
    entity_get_controller('taxonomy_vocabulary')->resetCache();
162
    $vocabulary = taxonomy_vocabulary_load($vid);
163
    $this->assertTrue($vocabulary, t('Vocabulary found in database'));
164 165 166

    // Delete the vocabulary.
    $edit = array();
167
    $this->drupalPost('admin/structure/taxonomy/' . $vocabulary->machine_name . '/edit', $edit, t('Delete'));
168 169
    $this->assertRaw(t('Are you sure you want to delete the vocabulary %name?', array('%name' => $vocabulary->name)), t('[confirm deletion] Asks for confirmation.'));
    $this->assertText(t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.'), t('[confirm deletion] Inform that all terms will be deleted.'));
170 171 172

    // Confirm deletion.
    $this->drupalPost(NULL, NULL, t('Delete'));
173
    $this->assertRaw(t('Deleted vocabulary %name.', array('%name' => $vocabulary->name)), t('Vocabulary deleted'));
174
    entity_get_controller('taxonomy_vocabulary')->resetCache();
175
    $this->assertFalse(taxonomy_vocabulary_load($vid), t('Vocabulary is not found in the database'));
176
  }
177 178 179 180 181 182
}


/**
 * Tests for taxonomy vocabulary functions.
 */
183
class TaxonomyVocabularyUnitTest extends TaxonomyWebTestCase {
184

185 186
  public static function getInfo() {
    return array(
187 188 189
      'name' => 'Taxonomy vocabularies',
      'description' => 'Test loading, saving and deleting vocabularies.',
      'group' => 'Taxonomy',
190 191
    );
  }
192 193

  function setUp() {
194
    parent::setUp('taxonomy', 'field_test');
195
    $admin_user = $this->drupalCreateUser(array('create article content', 'administer taxonomy'));
196
    $this->drupalLogin($admin_user);
197
    $this->vocabulary = $this->createVocabulary();
198 199 200 201 202 203 204 205
  }

  /**
   * Ensure that when an invalid vocabulary vid is loaded, it is possible
   * to load the same vid successfully if it subsequently becomes valid.
   */
  function testTaxonomyVocabularyLoadReturnFalse() {
    // Load a vocabulary that doesn't exist.
206
    $vocabularies = taxonomy_vocabulary_load_multiple(FALSE);
207 208 209
    $vid = count($vocabularies) + 1;
    $vocabulary = taxonomy_vocabulary_load($vid);
    // This should not return an object because no such vocabulary exists.
210
    $this->assertTrue(empty($vocabulary), t('No object loaded.'));
211 212

    // Create a new vocabulary.
213
    $this->createVocabulary();
214 215 216
    // Load the vocabulary with the same $vid from earlier.
    // This should return a vocabulary object since it now matches a real vid.
    $vocabulary = taxonomy_vocabulary_load($vid);
217 218
    $this->assertTrue(!empty($vocabulary) && is_object($vocabulary), t('Vocabulary is an object'));
    $this->assertTrue($vocabulary->vid == $vid, t('Valid vocabulary vid is the same as our previously invalid one.'));
219
  }
220

221 222 223 224 225
  /**
   * Test deleting a taxonomy that contains terms.
   */
  function testTaxonomyVocabularyDeleteWithTerms() {
    // Delete any existing vocabularies.
226
    foreach (taxonomy_vocabulary_load_multiple(FALSE) as $vocabulary) {
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
      taxonomy_vocabulary_delete($vocabulary->vid);
    }

    // Assert that there are no terms left.
    $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {taxonomy_term_data}')->fetchField());

    // Create a new vocabulary and add a few terms to it.
    $vocabulary = $this->createVocabulary();
    $terms = array();
    for ($i = 0; $i < 5; $i++) {
      $terms[$i] = $this->createTerm($vocabulary);
    }

    // Set up hierarchy. term 2 is a child of 1 and 4 a child of 1 and 2.
    $terms[2]->parent = array($terms[1]->tid);
    taxonomy_term_save($terms[2]);
    $terms[4]->parent = array($terms[1]->tid, $terms[2]->tid);
    taxonomy_term_save($terms[4]);

    // Assert that there are now 5 terms.
    $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {taxonomy_term_data}')->fetchField());

    taxonomy_vocabulary_delete($vocabulary->vid);

    // Assert that there are no terms left.
    $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {taxonomy_term_data}')->fetchField());
  }

255 256 257 258
  /**
   * Ensure that the vocabulary static reset works correctly.
   */
  function testTaxonomyVocabularyLoadStaticReset() {
259
    $original_vocabulary = taxonomy_vocabulary_load($this->vocabulary->vid);
260 261
    $this->assertTrue(is_object($original_vocabulary), t('Vocabulary loaded successfully'));
    $this->assertEqual($this->vocabulary->name, $original_vocabulary->name, t('Vocabulary loaded successfully'));
262 263

    // Change the name and description.
264 265 266 267
    $vocabulary = $original_vocabulary;
    $vocabulary->name = $this->randomName();
    $vocabulary->description = $this->randomName();
    taxonomy_vocabulary_save($vocabulary);
268

269 270
    // Load the vocabulary.
    $new_vocabulary = taxonomy_vocabulary_load($original_vocabulary->vid);
271 272
    $this->assertEqual($new_vocabulary->name, $vocabulary->name);
    $this->assertEqual($new_vocabulary->name, $vocabulary->name);
273 274 275

    // Delete the vocabulary.
    taxonomy_vocabulary_delete($this->vocabulary->vid);
276
    $vocabularies = taxonomy_vocabulary_load_multiple(FALSE);
277
    $this->assertTrue(!isset($vocabularies[$this->vocabulary->vid]), t('The vocabulary was deleted'));
278
  }
279 280 281 282 283 284 285

  /**
   * Tests for loading multiple vocabularies.
   */
  function testTaxonomyVocabularyLoadMultiple() {

    // Delete any existing vocabularies.
286
    foreach (taxonomy_vocabulary_load_multiple(FALSE) as $vocabulary) {
287 288 289 290 291 292 293 294 295 296 297 298 299 300
      taxonomy_vocabulary_delete($vocabulary->vid);
    }

    // Create some vocabularies and assign weights.
    $vocabulary1 = $this->createVocabulary();
    $vocabulary1->weight = 0;
    taxonomy_vocabulary_save($vocabulary1);
    $vocabulary2 = $this->createVocabulary();
    $vocabulary2->weight = 1;
    taxonomy_vocabulary_save($vocabulary2);
    $vocabulary3 = $this->createVocabulary();
    $vocabulary3->weight = 2;
    taxonomy_vocabulary_save($vocabulary3);

301 302 303
    // Fetch the names for all vocabularies, confirm that they are keyed by
    // machine name.
    $names = taxonomy_vocabulary_get_names();
304
    $this->assertEqual($names[$vocabulary1->machine_name]->name, $vocabulary1->name, t('Vocabulary 1 name found.'));
305

306
    // Fetch all of the vocabularies using taxonomy_vocabulary_load_multiple(FALSE).
307
    // Confirm that the vocabularies are ordered by weight.
308
    $vocabularies = taxonomy_vocabulary_load_multiple(FALSE);
309 310 311
    $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary1->vid, t('Vocabulary was found in the vocabularies array.'));
    $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary2->vid, t('Vocabulary was found in the vocabularies array.'));
    $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary3->vid, t('Vocabulary was found in the vocabularies array.'));
312 313 314 315

    // Fetch the vocabularies with taxonomy_vocabulary_load_multiple(), specifying IDs.
    // Ensure they are returned in the same order as the original array.
    $vocabularies = taxonomy_vocabulary_load_multiple(array($vocabulary3->vid, $vocabulary2->vid, $vocabulary1->vid));
316 317 318
    $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary3->vid, t('Vocabulary loaded successfully by ID.'));
    $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary2->vid, t('Vocabulary loaded successfully by ID.'));
    $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary1->vid, t('Vocabulary loaded successfully by ID.'));
319 320

    // Fetch vocabulary 1 by name.
321
    $vocabulary = current(taxonomy_vocabulary_load_multiple(array(), array('name' => $vocabulary1->name)));
322
    $this->assertTrue($vocabulary->vid == $vocabulary1->vid, t('Vocabulary loaded successfully by name.'));
323 324

    // Fetch vocabulary 1 by name and ID.
325
    $this->assertTrue(current(taxonomy_vocabulary_load_multiple(array($vocabulary1->vid), array('name' => $vocabulary1->name)))->vid == $vocabulary1->vid, t('Vocabulary loaded successfully by name and ID.'));
326
  }
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345

  /**
   * Tests that machine name changes are properly reflected.
   */
  function testTaxonomyVocabularyChangeMachineName() {
    // Add a field instance to the vocabulary.
    $field = array(
      'field_name' => 'field_test',
      'type' => 'test_field',
    );
    field_create_field($field);
    $instance = array(
      'field_name' => 'field_test',
      'entity_type' => 'taxonomy_term',
      'bundle' => $this->vocabulary->machine_name,
    );
    field_create_instance($instance);

    // Change the machine name.
346
    $old_name = $this->vocabulary->machine_name;
347 348 349 350
    $new_name = drupal_strtolower($this->randomName());
    $this->vocabulary->machine_name = $new_name;
    taxonomy_vocabulary_save($this->vocabulary);

351 352 353 354 355
    // Check that entity bundles are properly updated.
    $info = entity_get_info('taxonomy_term');
    $this->assertFalse(isset($info['bundles'][$old_name]), t('The old bundle name does not appear in entity_get_info().'));
    $this->assertTrue(isset($info['bundles'][$new_name]), t('The new bundle name appears in entity_get_info().'));

356 357 358
    // Check that the field instance is still attached to the vocabulary.
    $this->assertTrue(field_info_instance('taxonomy_term', 'field_test', $new_name), t('The bundle name was updated correctly.'));
  }
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377

  /**
   * Test uninstall and reinstall of the taxonomy module.
   */
  function testUninstallReinstall() {
    // Fields and field instances attached to taxonomy term bundles should be
    // removed when the module is uninstalled.
    $this->field_name = drupal_strtolower($this->randomName() . '_field_name');
    $this->field = array('field_name' => $this->field_name, 'type' => 'text', 'cardinality' => 4);
    $this->field = field_create_field($this->field);
    $this->instance = array(
      'field_name' => $this->field_name,
      'entity_type' => 'taxonomy_term',
      'bundle' => $this->vocabulary->machine_name,
      'label' => $this->randomName() . '_label',
    );
    field_create_instance($this->instance);

    module_disable(array('taxonomy'));
378
    require_once DRUPAL_ROOT . '/core/includes/install.inc';
379 380 381 382 383 384 385 386 387 388 389 390 391
    drupal_uninstall_modules(array('taxonomy'));
    module_enable(array('taxonomy'));

    // Now create a vocabulary with the same name. All field instances
    // connected to this vocabulary name should have been removed when the
    // module was uninstalled. Creating a new field with the same name and
    // an instance of this field on the same bundle name should be successful.
    unset($this->vocabulary->vid);
    taxonomy_vocabulary_save($this->vocabulary);
    unset($this->field['id']);
    field_create_field($this->field);
    field_create_instance($this->instance);
  }
392 393
}

394 395 396 397 398
/**
 * Unit tests for taxonomy term functions.
 */
class TaxonomyTermUnitTest extends TaxonomyWebTestCase {

399
  public static function getInfo() {
400
    return array(
401 402 403
      'name' => 'Taxonomy term unit tests',
      'description' => 'Unit tests for taxonomy term functions.',
      'group' => 'Taxonomy',
404 405
    );
  }
406 407 408 409 410 411 412 413 414 415 416 417

  function testTermDelete() {
    $vocabulary = $this->createVocabulary();
    $valid_term = $this->createTerm($vocabulary);
    // Delete a valid term.
    taxonomy_term_delete($valid_term->tid);
    $terms = taxonomy_term_load_multiple(array(), array('vid' => $vocabulary->vid));
    $this->assertTrue(empty($terms), 'Vocabulary is empty after deletion');

    // Delete an invalid term. Should not throw any notices.
    taxonomy_term_delete(42);
  }
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471

  /**
   * Test a taxonomy with terms that have multiple parents of different depths.
   */
  function testTaxonomyVocabularyTree() {
    // Create a new vocabulary with 6 terms.
    $vocabulary = $this->createVocabulary();
    $term = array();
    for ($i = 0; $i < 6; $i++) {
      $term[$i] = $this->createTerm($vocabulary);
    }

    // $term[2] is a child of 1 and 5.
    $term[2]->parent = array($term[1]->tid, $term[5]->tid);
    taxonomy_term_save($term[2]);
    // $term[3] is a child of 2.
    $term[3]->parent = array($term[2]->tid);
    taxonomy_term_save($term[3]);
    // $term[5] is a child of 4.
    $term[5]->parent = array($term[4]->tid);
    taxonomy_term_save($term[5]);

    /**
     * Expected tree:
     * term[0] | depth: 0
     * term[1] | depth: 0
     * -- term[2] | depth: 1
     * ---- term[3] | depth: 2
     * term[4] | depth: 0
     * -- term[5] | depth: 1
     * ---- term[2] | depth: 2
     * ------ term[3] | depth: 3
     */

    // Count $term[1] parents with $max_depth = 1.
    $tree = taxonomy_get_tree($vocabulary->vid, $term[1]->tid, 1);
    $this->assertEqual(1, count($tree), 'We have one parent with depth 1.');

    // Count all vocabulary tree elements.
    $tree = taxonomy_get_tree($vocabulary->vid);
    $this->assertEqual(8, count($tree), 'We have all vocabulary tree elements.');

    // Count elements in every tree depth.
    foreach($tree as $element) {
      if (!isset($depth_count[$element->depth])) {
        $depth_count[$element->depth] = 0;
      }
      $depth_count[$element->depth]++;
    }
    $this->assertEqual(3, $depth_count[0], 'Three elements in taxonomy tree depth 0.');
    $this->assertEqual(2, $depth_count[1], 'Two elements in taxonomy tree depth 1.');
    $this->assertEqual(2, $depth_count[2], 'Two elements in taxonomy tree depth 2.');
    $this->assertEqual(1, $depth_count[3], 'One element in taxonomy tree depth 3.');
   }
472 473
}

474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510
/**
 * Test for legacy node bug.
 */
class TaxonomyLegacyTestCase extends TaxonomyWebTestCase {

  public static function getInfo() {
    return array(
      'name' => 'Test for legacy node bug.',
      'description' => 'Posts an article with a taxonomy term and a date prior to 1970.',
      'group' => 'Taxonomy',
    );
  }

  function setUp() {
    parent::setUp('taxonomy');
    $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'administer nodes', 'bypass node access'));
    $this->drupalLogin($this->admin_user);
  }

  /**
   * Test taxonomy functionality with nodes prior to 1970.
   */
  function testTaxonomyLegacyNode() {
    // Posts an article with a taxonomy term and a date prior to 1970.
    $langcode = LANGUAGE_NONE;
    $edit = array();
    $edit['title'] = $this->randomName();
    $edit['date'] = '1969-01-01 00:00:00 -0500';
    $edit["body[$langcode][0][value]"] = $this->randomName();
    $edit["field_tags[$langcode]"] = $this->randomName();
    $this->drupalPost('node/add/article', $edit, t('Save'));
    // Checks that the node has been saved.
    $node = $this->drupalGetNodeByTitle($edit['title']);
    $this->assertEqual($node->created, strtotime($edit['date']), t('Legacy node was saved with the right date.'));
  }
}

511 512 513
/**
 * Tests for taxonomy term functions.
 */
514
class TaxonomyTermTestCase extends TaxonomyWebTestCase {
515

516
  public static function getInfo() {
517
    return array(
518 519
      'name' => 'Taxonomy term functions and forms.',
      'description' => 'Test load, save and delete for taxonomy terms.',
520
      'group' => 'Taxonomy',
521 522 523
    );
  }

524 525
  function setUp() {
    parent::setUp('taxonomy');
526
    $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access'));
527 528
    $this->drupalLogin($this->admin_user);
    $this->vocabulary = $this->createVocabulary();
529

530 531 532 533 534 535 536
    $field = array(
      'field_name' => 'taxonomy_' . $this->vocabulary->machine_name,
      'type' => 'taxonomy_term_reference',
      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
      'settings' => array(
        'allowed_values' => array(
          array(
537
            'vocabulary' => $this->vocabulary->machine_name,
538 539 540 541 542 543 544
            'parent' => 0,
          ),
        ),
      ),
    );
    field_create_field($field);

545 546 547
    $this->instance = array(
      'field_name' => 'taxonomy_' . $this->vocabulary->machine_name,
      'bundle' => 'article',
548
      'entity_type' => 'node',
549 550 551 552
      'widget' => array(
        'type' => 'options_select',
      ),
      'display' => array(
553
        'default' => array(
554
          'type' => 'taxonomy_term_reference_link',
555 556 557 558
        ),
      ),
    );
    field_create_instance($this->instance);
559 560 561
  }

  /**
562
   * Test terms in a single and multiple hierarchy.
563
   */
564 565
  function testTaxonomyTermHierarchy() {
    // Create two taxonomy terms.
566 567
    $term1 = $this->createTerm($this->vocabulary);
    $term2 = $this->createTerm($this->vocabulary);
568 569

    // Edit $term2, setting $term1 as parent.
570
    $edit = array();
571
    $edit['parent[]'] = array($term1->tid);
572 573 574
    $this->drupalPost('taxonomy/term/' . $term2->tid . '/edit', $edit, t('Save'));

    // Check the hierarchy.
575 576
    $children = taxonomy_term_load_children($term1->tid);
    $parents = taxonomy_term_load_parents($term2->tid);
577 578
    $this->assertTrue(isset($children[$term2->tid]), t('Child found correctly.'));
    $this->assertTrue(isset($parents[$term1->tid]), t('Parent found correctly.'));
579

580 581 582
    // Load and save a term, confirming that parents are still set.
    $term = taxonomy_term_load($term2->tid);
    taxonomy_term_save($term);
583
    $parents = taxonomy_term_load_parents($term2->tid);
584
    $this->assertTrue(isset($parents[$term1->tid]), t('Parent found correctly.'));
585

586
    // Create a third term and save this as a parent of term2.
587
    $term3 = $this->createTerm($this->vocabulary);
588 589
    $term2->parent = array($term1->tid, $term3->tid);
    taxonomy_term_save($term2);
590
    $parents = taxonomy_term_load_parents($term2->tid);
591
    $this->assertTrue(isset($parents[$term1->tid]) && isset($parents[$term3->tid]), t('Both parents found successfully.'));
592 593
  }

594
  /**
595
   * Test that hook_node_$op implementations work correctly.
596
   *
597 598 599
   * Save & edit a node and assert that taxonomy terms are saved/loaded properly.
   */
  function testTaxonomyNode() {
600
    // Create two taxonomy terms.
601 602
    $term1 = $this->createTerm($this->vocabulary);
    $term2 = $this->createTerm($this->vocabulary);
603 604

    // Post an article.
605
    $edit = array();
606
    $langcode = LANGUAGE_NONE;
607
    $edit["title"] = $this->randomName();
608
    $edit["body[$langcode][0][value]"] = $this->randomName();
609
    $edit[$this->instance['field_name'] . '[' . $langcode .'][]'] = $term1->tid;
610 611
    $this->drupalPost('node/add/article', $edit, t('Save'));

612
    // Check that the term is displayed when the node is viewed.
613
    $node = $this->drupalGetNodeByTitle($edit["title"]);
614
    $this->drupalGet('node/' . $node->nid);
615
    $this->assertText($term1->name, t('Term is displayed when viewing the node.'));
616

617
    // Edit the node with a different term.
618
    $edit[$this->instance['field_name'] . '[' . $langcode . '][]'] = $term2->tid;
619
    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
620

621
    $this->drupalGet('node/' . $node->nid);
622
    $this->assertText($term2->name, t('Term is displayed when viewing the node.'));
623

624
    // Preview the node.
625
    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Preview'));
626
    $this->assertNoUniqueText($term2->name, t('Term is displayed when previewing the node.'));
627
    $this->drupalPost(NULL, NULL, t('Preview'));
628
    $this->assertNoUniqueText($term2->name, t('Term is displayed when previewing the node again.'));
629 630
  }

631 632 633
  /**
   * Test term creation with a free-tagging vocabulary from the node form.
   */
634
  function testNodeTermCreationAndDeletion() {
635
    // Enable tags in the vocabulary.
636 637 638 639
    $instance = $this->instance;
    $instance['widget'] = array('type' => 'taxonomy_autocomplete');
    $instance['bundle'] = 'page';
    field_create_instance($instance);
640
    $terms = array(
641 642 643
      'term1' => $this->randomName(),
      'term2' => $this->randomName() . ', ' . $this->randomName(),
      'term3' => $this->randomName(),
644
    );
645

646
    $edit = array();
647
    $langcode = LANGUAGE_NONE;
648
    $edit["title"] = $this->randomName();
649
    $edit["body[$langcode][0][value]"] = $this->randomName();
650 651
    // Insert the terms in a comma separated list. Vocabulary 1 is a
    // free-tagging field created by the default profile.
652
    $edit[$instance['field_name'] . "[$langcode]"] = drupal_implode_tags($terms);
653 654 655 656

    // Preview and verify the terms appear but are not created.
    $this->drupalPost('node/add/page', $edit, t('Preview'));
    foreach ($terms as $term) {
657
      $this->assertText($term, t('The term appears on the node preview'));
658 659
    }
    $tree = taxonomy_get_tree($this->vocabulary->vid);
660
    $this->assertTrue(empty($tree), t('The terms are not created on preview.'));
661 662 663 664 665

    // taxonomy.module does not maintain its static caches.
    drupal_static_reset();

    // Save, creating the terms.
666
    $this->drupalPost('node/add/page', $edit, t('Save'));
667
    $this->assertRaw(t('@type %title has been created.', array('@type' => t('Basic page'), '%title' => $edit["title"])), t('The node was created successfully'));
668
    foreach ($terms as $term) {
669
      $this->assertText($term, t('The term was saved and appears on the node page'));
670
    }
671 672

    // Get the created terms.
673 674
    $term_objects = array();
    foreach ($terms as $key => $term) {
675
      $term_objects[$key] = taxonomy_term_load_multiple_by_name($term);
676 677
      $term_objects[$key] = reset($term_objects[$key]);
    }
678

679
    // Delete term 1.
680
    $this->drupalPost('taxonomy/term/' . $term_objects['term1']->tid . '/edit', array(), t('Delete'));
681
    $this->drupalPost(NULL, NULL, t('Delete'));
682
    $term_names = array($term_objects['term2']->name, $term_objects['term3']->name);
683 684 685 686 687 688

    // Get the node.
    $node = $this->drupalGetNodeByTitle($edit["title"]);
    $this->drupalGet('node/' . $node->nid);

    foreach ($term_names as $term_name) {
689
      $this->assertText($term_name, t('The term %name appears on the node page after one term %deleted was deleted', array('%name' => $term_name, '%deleted' => $term_objects['term1']->name)));
690
    }
691
    $this->assertNoText($term_objects['term1']->name, t('The deleted term %name does not appear on the node page.', array('%name' => $term_objects['term1']->name)));
692

693 694
    // Test autocomplete on term 2, which contains a comma.
    // The term will be quoted, and the " will be encoded in unicode (\u0022).
695
    $input = substr($term_objects['term2']->name, 0, 3);
696
    $this->drupalGet('taxonomy/autocomplete/taxonomy_' . $this->vocabulary->machine_name . '/' . $input);
697
    $this->assertRaw('{"\u0022' . $term_objects['term2']->name . '\u0022":"' . $term_objects['term2']->name . '"}', t('Autocomplete returns term %term_name after typing the first 3 letters.', array('%term_name' => $term_objects['term2']->name)));
698 699 700

    // Test autocomplete on term 3 - it is alphanumeric only, so no extra
    // quoting.
701
    $input = substr($term_objects['term3']->name, 0, 3);
702
    $this->drupalGet('taxonomy/autocomplete/taxonomy_' . $this->vocabulary->machine_name . '/' . $input);
703
    $this->assertRaw('{"' . $term_objects['term3']->name . '":"' . $term_objects['term3']->name . '"}', t('Autocomplete returns term %term_name after typing the first 3 letters.', array('%term_name' => $term_objects['term3']->name)));
704 705
  }

706
  /**
707
   * Save, edit and delete a term using the user interface.
708
   */
709
  function testTermInterface() {
710 711
    $edit = array(
      'name' => $this->randomName(12),
712
      'description[value]' => $this->randomName(100),
713
    );
714
    // Explicitly set the parents field to 'root', to ensure that
715
    // taxonomy_form_term_submit() handles the invalid term ID correctly.
716
    $edit['parent[]'] = array(0);
717

718
    // Create the term to edit.
719
    $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/add', $edit, t('Save'));
720

721
    $terms = taxonomy_term_load_multiple_by_name($edit['name']);
722
    $term = reset($terms);
723
    $this->assertNotNull($term, t('Term found in database'));
724 725

    // Submitting a term takes us to the add page; we need the List page.
726
    $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name);
727 728 729 730 731 732

    // Test edit link as accessed from Taxonomy administration pages.
    // Because Simpletest creates its own database when running tests, we know
    // the first edit link found on the listing page is to our term.
    $this->clickLink(t('edit'));

733
    $this->assertRaw($edit['name'], t('The randomly generated term name is present.'));
734
    $this->assertText($edit['description[value]'], t('The randomly generated term description is present.'));
735 736 737

    $edit = array(
      'name' => $this->randomName(14),
738
      'description[value]' => $this->randomName(102),
739 740 741
    );

    // Edit the term.
742
    $this->drupalPost('taxonomy/term/' . $term->tid . '/edit', $edit, t('Save'));
743

744 745
    // Check that the term is still present at admin UI after edit.
    $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name);
746
    $this->assertText($edit['name'], t('The randomly generated term name is present.'));
747 748
    $this->assertLink(t('edit'));

749
    // View the term and check that it is correct.
750
    $this->drupalGet('taxonomy/term/' . $term->tid);
751 752
    $this->assertText($edit['name'], t('The randomly generated term name is present.'));
    $this->assertText($edit['description[value]'], t('The randomly generated term description is present.'));
753

754
    // Did this page request display a 'term-listing-heading'?
755
    $this->assertPattern('|class="taxonomy-term-description"|', 'Term page displayed the term description element.');
756 757 758 759
    // Check that it does NOT show a description when description is blank.
    $term->description = '';
    taxonomy_term_save($term);
    $this->drupalGet('taxonomy/term/' . $term->tid);
760
    $this->assertNoPattern('|class="taxonomy-term-description"|', 'Term page did not display the term description when description was blank.');
761

762
    // Check that the term feed page is working.
763 764
    $this->drupalGet('taxonomy/term/' . $term->tid . '/feed');

765 766 767 768
    // Check that the term edit page does not try to interpret additional path
    // components as arguments for taxonomy_form_term().
    $this->drupalGet('taxonomy/term/' . $term->tid . '/edit/' . $this->randomName());

769
    // Delete the term.
770
    $this->drupalPost('taxonomy/term/' . $term->tid . '/edit', array(), t('Delete'));
771 772 773
    $this->drupalPost(NULL, NULL, t('Delete'));

    // Assert that the term no longer exists.
774
    $this->drupalGet('taxonomy/term/' . $term->tid);
775
    $this->assertResponse(404, t('The taxonomy term page was not found'));
776
  }
777

778 779 780 781 782 783 784 785 786 787 788 789 790 791 792
  /**
   * Save, edit and delete a term using the user interface.
   */
  function testTermReorder() {
    $this->createTerm($this->vocabulary);
    $this->createTerm($this->vocabulary);
    $this->createTerm($this->vocabulary);

    // Fetch the created terms in the default alphabetical order, i.e. term1
    // precedes term2 alphabetically, and term2 precedes term3.
    drupal_static_reset('taxonomy_get_tree');
    drupal_static_reset('taxonomy_get_treeparent');
    drupal_static_reset('taxonomy_get_treeterms');
    list($term1, $term2, $term3) = taxonomy_get_tree($this->vocabulary->vid);

793
    $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name);
794 795 796 797 798

    // Each term has four hidden fields, "tid:1:0[tid]", "tid:1:0[parent]",
    // "tid:1:0[depth]", and "tid:1:0[weight]". Change the order to term2,
    // term3, term1 by setting weight property, make term3 a child of term2 by
    // setting the parent and depth properties, and update all hidden fields.
799 800 801 802
    $edit = array(
      'tid:' . $term2->tid . ':0[tid]' => $term2->tid,
      'tid:' . $term2->tid . ':0[parent]' => 0,
      'tid:' . $term2->tid . ':0[depth]' => 0,
803
      'tid:' . $term2->tid . ':0[weight]' => 0,
804 805 806
      'tid:' . $term3->tid . ':0[tid]' => $term3->tid,
      'tid:' . $term3->tid . ':0[parent]' => $term2->tid,
      'tid:' . $term3->tid . ':0[depth]' => 1,
807
      'tid:' . $term3->tid . ':0[weight]' => 1,
808 809 810
      'tid:' . $term1->tid . ':0[tid]' => $term1->tid,
      'tid:' . $term1->tid . ':0[parent]' => 0,
      'tid:' . $term1->tid . ':0[depth]' => 0,
811
      'tid:' . $term1->tid . ':0[weight]' => 2,
812 813 814 815 816 817 818
    );
    $this->drupalPost(NULL, $edit, t('Save'));

    drupal_static_reset('taxonomy_get_tree');
    drupal_static_reset('taxonomy_get_treeparent');
    drupal_static_reset('taxonomy_get_treeterms');
    $terms = taxonomy_get_tree($this->vocabulary->vid);
819 820 821
    $this->assertEqual($terms[0]->tid, $term2->tid, t('Term 2 was moved above term 1.'));
    $this->assertEqual($terms[1]->parents, array($term2->tid), t('Term 3 was made a child of term 2.'));
    $this->assertEqual($terms[2]->tid, $term1->tid, t('Term 1 was moved below term 2.'));
822

823
    $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name, array(), t('Reset to alphabetical'));
824 825 826 827 828 829 830
    // Submit confirmation form.
    $this->drupalPost(NULL, array(), t('Reset to alphabetical'));

    drupal_static_reset('taxonomy_get_tree');
    drupal_static_reset('taxonomy_get_treeparent');
    drupal_static_reset('taxonomy_get_treeterms');
    $terms = taxonomy_get_tree($this->vocabulary->vid);
831 832 833 834
    $this->assertEqual($terms[0]->tid, $term1->tid, t('Term 1 was moved to back above term 2.'));
    $this->assertEqual($terms[1]->tid, $term2->tid, t('Term 2 was moved to back below term 1.'));
    $this->assertEqual($terms[2]->tid, $term3->tid, t('Term 3 is still below term 2.'));
    $this->assertEqual($terms[2]->parents, array($term2->tid), t('Term 3 is still a child of term 2.').var_export($terms[1]->tid,1));
835 836
  }

837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853
  /**
   * Test saving a term with multiple parents through the UI.
   */
  function testTermMultipleParentsInterface() {
    // Add a new term to the vocabulary so that we can have multiple parents.
    $parent = $this->createTerm($this->vocabulary);

    // Add a new term with multiple parents.
    $edit = array(
      'name' => $this->randomName(12),
      'description[value]' => $this->randomName(100),
      'parent[]' => array(0, $parent->tid),
    );
    // Save the new term.
    $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/add', $edit, t('Save'));

    // Check that the term was successfully created.
854
    $terms = taxonomy_term_load_multiple_by_name($edit['name']);
855 856 857 858 859
    $term = reset($terms);
    $this->assertNotNull($term, t('Term found in database'));
    $this->assertEqual($edit['name'], $term->name, t('Term name was successfully saved.'));
    $this->assertEqual($edit['description[value]'], $term->description, t('Term description was successfully saved.'));
    // Check that the parent tid is still there. The other parent (<root>) is
860 861
    // not added by taxonomy_term_load_parents().
    $parents = taxonomy_term_load_parents($term->tid);
862 863 864 865
    $parent = reset($parents);
    $this->assertEqual($edit['parent[]'][1], $parent->tid, t('Term parents were successfully saved.'));
  }

866
  /**
867
   * Test taxonomy_term_load_multiple_by_name().
868 869
   */
  function testTaxonomyGetTermByName() {
870
    $term = $this->createTerm($this->vocabulary);
871 872

    // Load the term with the exact name.
873
    $terms = taxonomy_term_load_multiple_by_name($term->name);
874
    $this->assertTrue(isset($terms[$term->tid]), t('Term loaded using exact name.'));
875 876

    // Load the term with space concatenated.
877
    $terms  = taxonomy_term_load_multiple_by_name('  ' . $term->name . '   ');
878
    $this->assertTrue(isset($terms[$term->tid]), t('Term loaded with extra whitespace.'));
879 880

    // Load the term with name uppercased.
881
    $terms = taxonomy_term_load_multiple_by_name(strtoupper($term->name));
882
    $this->assertTrue(isset($terms[$term->tid]), t('Term loaded with uppercased name.'));
883 884

    // Load the term with name lowercased.
885
    $terms = taxonomy_term_load_multiple_by_name(strtolower($term->name));
886
    $this->assertTrue(isset($terms[$term->tid]), t('Term loaded with lowercased name.'));
887 888

    // Try to load an invalid term name.
889
    $terms = taxonomy_term_load_multiple_by_name('Banana');
890 891 892
    $this->assertFalse($terms);

    // Try to load the term using a substring of the name.
893
    $terms = taxonomy_term_load_multiple_by_name(drupal_substr($term->name, 2));
894
    $this->assertFalse($terms);
895 896 897 898 899 900 901 902 903

    // Create a new term in a different vocabulary with the same name.
    $new_vocabulary = $this->createVocabulary();
    $new_term = new stdClass();
    $new_term->name = $term->name;
    $new_term->vid = $new_vocabulary->vid;
    taxonomy_term_save($new_term);

    // Load multiple terms with the same name.
904
    $terms = taxonomy_term_load_multiple_by_name($term->name);
905 906 907
    $this->assertEqual(count($terms), 2, t('Two terms loaded with the same name.'));

    // Load single term when restricted to one vocabulary.
908
    $terms = taxonomy_term_load_multiple_by_name($term->name, $this->vocabulary->machine_name);
909 910 911 912 913 914 915 916
    $this->assertEqual(count($terms), 1, t('One term loaded when restricted by vocabulary.'));
    $this->assertTrue(isset($terms[$term->tid]), t('Term loaded using exact name and vocabulary machine name.'));

    // Create a new term with another name.
    $term2 = $this->createTerm($this->vocabulary);

    // Try to load a term by name that doesn't exist in this vocabulary but
    // exists in another vocabulary.
917
    $terms = taxonomy_term_load_multiple_by_name($term2->name, $new_vocabulary->machine_name);
918 919 920
    $this->assertFalse($terms, t('Invalid term name restricted by vocabulary machine name not loaded.'));

    // Try to load terms filtering by a non-existing vocabulary.
921
    $terms = taxonomy_term_load_multiple_by_name($term2->name, 'non_existing_vocabulary');
922
    $this->assertEqual(count($terms), 0, t('No terms loaded when restricted by a non-existing vocabulary.'));
923
  }
924
}
925

926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964