/**
  * Tests creating a feature via UI and download it.
  */
 public function testCreateFeaturesUI()
 {
     $feature_name = 'test_feature2';
     $admin_user = $this->createUser(['administer site configuration', 'export configuration', 'administer modules']);
     $this->drupalLogin($admin_user);
     $this->drupalPlaceBlock('local_actions_block');
     $this->drupalGet('admin/config/development/features');
     $this->clickLink('Create new feature');
     $this->assertResponse(200);
     $edit = ['name' => 'Test feature', 'machine_name' => $feature_name, 'description' => 'Test description: <strong>giraffe</strong>', 'version' => '8.x-1.0', 'system_simple[sources][selected][system.theme]' => TRUE, 'system_simple[sources][selected][user.settings]' => TRUE];
     $this->drupalPostForm(NULL, $edit, $this->t('Download Archive'));
     $this->assertResponse(200);
     $archive = $this->getRawContent();
     $filename = tempnam($this->tempFilesDirectory, 'feature');
     file_put_contents($filename, $archive);
     $archive = new ArchiveTar($filename);
     $files = $archive->listContent();
     $this->assertEqual(4, count($files));
     $this->assertEqual($feature_name . '/' . $feature_name . '.info.yml', $files[0]['filename']);
     $this->assertEqual($feature_name . '/' . $feature_name . '.features.yml', $files[1]['filename']);
     $this->assertEqual($feature_name . '/config/install/system.theme.yml', $files[2]['filename']);
     $this->assertEqual($feature_name . '/config/install/user.settings.yml', $files[3]['filename']);
     // Ensure that the archive contains the expected values.
     $info_filename = tempnam($this->tempFilesDirectory, 'feature');
     file_put_contents($info_filename, $archive->extractInString($feature_name . '/' . $feature_name . '.info.yml'));
     $features_info_filename = tempnam($this->tempFilesDirectory, 'feature');
     file_put_contents($features_info_filename, $archive->extractInString($feature_name . '/' . $feature_name . '.features.yml'));
     /** @var \Drupal\Core\Extension\InfoParser $info_parser */
     $info_parser = \Drupal::service('info_parser');
     $parsed_info = $info_parser->parse($info_filename);
     $this->assertEqual('Test feature', $parsed_info['name']);
     $parsed_features_info = Yaml::decode(file_get_contents($features_info_filename));
     $this->assertEqual(['required' => ['system.theme', 'user.settings']], $parsed_features_info);
     $archive->extract(\Drupal::service('kernel')->getSitePath() . '/modules');
     $module_path = \Drupal::service('kernel')->getSitePath() . '/modules/' . $feature_name;
     // Ensure that the features listing renders the right content.
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertLink('Test feature');
     $this->assertEqual($feature_name, (string) $tr->children()[2]);
     $description_column = (string) $tr->children()[3]->asXml();
     $this->assertTrue(strpos($description_column, 'system.theme') !== FALSE);
     $this->assertTrue(strpos($description_column, 'user.settings') !== FALSE);
     $this->assertRaw('Test description: <strong>giraffe</strong>');
     $this->assertEqual('Uninstalled', (string) $tr->children()[5]);
     $this->assertEqual('', (string) $tr->children()[6]);
     // Remove one and add new configuration.
     $this->clickLink('Test feature');
     $edit = ['system_simple[included][system.theme]' => FALSE, 'user_role[sources][selected][authenticated]' => TRUE];
     $this->drupalPostForm(NULL, $edit, $this->t('Write'));
     $info_filename = $module_path . '/' . $feature_name . '.info.yml';
     $parsed_info = $info_parser->parse($info_filename);
     $this->assertEqual('Test feature', $parsed_info['name']);
     $features_info_filename = $module_path . '/' . $feature_name . '.features.yml';
     $parsed_features_info = Yaml::decode(file_get_contents($features_info_filename));
     $this->assertEqual(['required' => ['user.settings', 'user.role.authenticated']], $parsed_features_info);
     $this->drupalGet('admin/modules');
     $edit = ['modules[Other][' . $feature_name . '][enable]' => TRUE];
     $this->drupalPostForm(NULL, $edit, $this->t('Install'));
     // Check that the feature is listed as installed.
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertEqual('Installed', (string) $tr->children()[5]);
     // Check that a config change results in a feature marked as changed.
     \Drupal::configFactory()->getEditable('user.settings')->set('anonymous', 'Anonymous giraffe')->save();
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertTrue(strpos($tr->children()[6]->asXml(), 'Changed') !== FALSE);
     // Uninstall module.
     $this->drupalPostForm('admin/modules/uninstall', ['uninstall[' . $feature_name . ']' => TRUE], $this->t('Uninstall'));
     $this->drupalPostForm(NULL, [], $this->t('Uninstall'));
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertTrue(strpos($tr->children()[6]->asXml(), 'Changed') !== FALSE);
     $this->clickLink($this->t('Changed'));
     $this->assertRaw('<td class="diff-context diff-deletedline">anonymous : Anonymous <span class="diffchange">giraffe</span></td>');
     $this->assertRaw('<td class="diff-context diff-addedline">anonymous : Anonymous</td>');
     $this->drupalGet('admin/modules');
     $edit = ['modules[Other][' . $feature_name . '][enable]' => TRUE];
     $this->drupalPostForm(NULL, $edit, $this->t('Install'));
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertEqual('Installed', (string) $tr->children()[5]);
     // Ensure that the changed config got overridden.
     $this->assertEqual('Anonymous', \Drupal::config('user.settings')->get('anonymous'));
     // Change the value, export and ensure that its not shown as changed.
     \Drupal::configFactory()->getEditable('user.settings')->set('anonymous', 'Anonymous giraffe')->save();
     // Ensure that exporting this change will result in an unchanged feature.
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertTrue(strpos($tr->children()[6]->asXml(), 'Changed') !== FALSE);
     $this->clickLink('Test feature');
     $this->drupalPostForm(NULL, [], $this->t('Write'));
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertEqual('Installed', (string) $tr->children()[5]);
 }
 /**
  * @covers \Drupal\features\Plugin\FeaturesGeneration\FeaturesGenerationWrite
  */
 public function testExportWrite()
 {
     // Set a fake drupal root, so the testbot can also write into it.
     vfsStream::setup('drupal');
     \Drupal::getContainer()->set('app.root', 'vfs://drupal');
     $this->featuresManager->setRoot('vfs://drupal');
     $package = $this->featuresManager->getPackage(self::PACKAGE_NAME);
     // Find out where package will be exported
     list($full_name, $path) = $this->featuresManager->getExportInfo($package, $this->assigner->getBundle());
     $path = 'vfs://drupal/' . $path . '/' . $full_name;
     if (file_exists($path)) {
         file_unmanaged_delete_recursive($path);
     }
     $this->assertFalse(file_exists($path), 'Package directory already exists.');
     $this->generator->generatePackages('write', $this->assigner->getBundle(), [self::PACKAGE_NAME]);
     $info_file_uri = $path . '/' . self::PACKAGE_NAME . '.info.yml';
     $this->assertTrue(file_exists($path), 'Package directory was not generated.');
     $this->assertTrue(file_exists($info_file_uri), 'Package info.yml not generated.');
     $this->assertTrue(file_exists($path . '/config/install'), 'Package config/install not generated.');
     $this->assertTrue(file_exists($path . '/config/install/system.site.yml'), 'Config.yml not exported.');
     $expected_info = ["name" => "My test package", "type" => "module", "core" => "8.x"];
     $info = Yaml::decode(file_get_contents($info_file_uri));
     $this->assertEquals($expected_info, $info, 'Incorrect info file generated');
     // Now, add stuff to the feature and re-export to ensure it is preserved
     // Add a dependency to the package itself to see that it gets exported.
     $package->setDependencies(['user']);
     $this->featuresManager->setPackage($package);
     // Add dependency and custom key to the info file to simulate manual edit.
     $info['dependencies'] = ['node'];
     $info['mykey'] = "test value";
     $info_contents = Yaml::encode($info);
     file_put_contents($info_file_uri, $info_contents);
     // Add an extra file that should be retained.
     $css_file = $path . '/' . self::PACKAGE_NAME . '.css';
     $file_contents = "This is a dummy file";
     file_put_contents($css_file, $file_contents);
     // Add a config file that should be removed since it's not part of the
     // feature.
     $config_file = $path . '/config/install/node.type.mytype.yml';
     file_put_contents($config_file, $file_contents);
     $this->generator->generatePackages('write', $this->assigner->getBundle(), [self::PACKAGE_NAME]);
     $this->assertTrue(file_exists($info_file_uri), 'Package info.yml not generated.');
     $expected_info = ["name" => "My test package", "type" => "module", "core" => "8.x", "dependencies" => ["node", "user"], "mykey" => "test value"];
     $info = Yaml::decode(file_get_contents($info_file_uri));
     $this->assertEquals($expected_info, $info, 'Incorrect info file generated');
     $this->assertTrue(file_exists($css_file), 'Extra file was not retained.');
     $this->assertFalse(file_exists($config_file), 'Config directory was not cleaned.');
     $this->assertEquals($file_contents, file_get_contents($css_file), 'Extra file contents not retained');
     // Next, test that generating an Archive picks up the extra files.
     $filename = file_directory_temp() . '/' . self::PACKAGE_NAME . '.tar.gz';
     if (file_exists($filename)) {
         unlink($filename);
     }
     $this->assertFalse(file_exists($filename), 'Archive file already exists.');
     $this->generator->generatePackages('archive', $this->assigner->getBundle(), [self::PACKAGE_NAME]);
     $this->assertTrue(file_exists($filename), 'Archive file was not generated.');
     $archive = new ArchiveTar($filename);
     $files = $archive->listContent();
     $this->assertEquals(4, count($files));
     $this->assertEquals(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.info.yml', $files[0]['filename']);
     $this->assertEquals(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.features.yml', $files[1]['filename']);
     $this->assertEquals(self::PACKAGE_NAME . '/config/install/system.site.yml', $files[2]['filename']);
     $this->assertEquals(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.css', $files[3]['filename']);
     $expected_info = ["name" => "My test package", "type" => "module", "core" => "8.x", "dependencies" => ["node", "user"], "mykey" => "test value"];
     $info = Yaml::decode($archive->extractInString(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.info.yml'));
     $this->assertEquals($expected_info, $info, 'Incorrect info file generated');
     $this->assertEquals($file_contents, $archive->extractInString(self::PACKAGE_NAME . '/' . self::PACKAGE_NAME . '.css'), 'Extra file contents not retained');
 }