class GenerateThemeTest

Same name in this branch
  1. 10 core/tests/Drupal/BuildTests/Command/GenerateThemeTest.php \Drupal\BuildTests\Command\GenerateThemeTest
Same name in other branches
  1. 9 core/tests/Drupal/Tests/Core/Command/GenerateThemeTest.php \Drupal\Tests\Core\Command\GenerateThemeTest
  2. 11.x core/tests/Drupal/Tests/Core/Command/GenerateThemeTest.php \Drupal\Tests\Core\Command\GenerateThemeTest
  3. 11.x core/tests/Drupal/BuildTests/Command/GenerateThemeTest.php \Drupal\BuildTests\Command\GenerateThemeTest

Tests the generate-theme commands.

@requires extension pdo_sqlite

@group Command

Hierarchy

Expanded class hierarchy of GenerateThemeTest

File

core/tests/Drupal/Tests/Core/Command/GenerateThemeTest.php, line 20

Namespace

Drupal\Tests\Core\Command
View source
class GenerateThemeTest extends QuickStartTestBase {
    
    /**
     * The PHP executable path.
     *
     * @var string
     */
    protected $php;
    
    /**
     * {@inheritdoc}
     */
    protected function setUp() : void {
        $sqlite = (new \PDO('sqlite::memory:'))->query('select sqlite_version()')
            ->fetch()[0];
        if (version_compare($sqlite, Tasks::SQLITE_MINIMUM_VERSION) < 0) {
            $this->markTestSkipped();
        }
        parent::setUp();
        $php_executable_finder = new PhpExecutableFinder();
        $this->php = $php_executable_finder->find();
        $this->copyCodebase();
        $this->executeCommand('COMPOSER_DISCARD_CHANGES=true composer install --no-dev --no-interaction');
        chdir($this->getWorkingPath());
    }
    
    /**
     * Generates PHP process to generate a theme from core's starterkit theme.
     *
     * @return \Symfony\Component\Process\Process
     *   The PHP process
     */
    private function generateThemeFromStarterkit($env = NULL) : Process {
        $install_command = [
            $this->php,
            'core/scripts/drupal',
            'generate-theme',
            'test_custom_theme',
            '--name="Test custom starterkit theme"',
            '--description="Custom theme generated from a starterkit theme"',
        ];
        $process = new Process($install_command, NULL, $env);
        $process->setTimeout(60);
        return $process;
    }
    
    /**
     * Asserts the theme exists. Returns the parsed *.info.yml file.
     *
     * @param string $theme_path_relative
     *   The core-relative path to the theme.
     *
     * @return array
     *   The parsed *.info.yml file.
     */
    private function assertThemeExists(string $theme_path_relative) : array {
        $theme_path_absolute = $this->getWorkspaceDirectory() . "/{$theme_path_relative}";
        $theme_name = basename($theme_path_relative);
        $info_yml_filename = "{$theme_name}.info.yml";
        $this->assertFileExists($theme_path_absolute . '/' . $info_yml_filename);
        $info = Yaml::decode(file_get_contents($theme_path_absolute . '/' . $info_yml_filename));
        return $info;
    }
    
    /**
     * Tests the generate-theme command.
     */
    public function test() {
        // Do not rely on \Drupal::VERSION: change the version to a concrete version
        // number, to simulate using a tagged core release.
        $starterkit_info_yml = $this->getWorkspaceDirectory() . '/core/themes/starterkit_theme/starterkit_theme.info.yml';
        $info = Yaml::decode(file_get_contents($starterkit_info_yml));
        $info['version'] = '9.4.0';
        file_put_contents($starterkit_info_yml, Yaml::encode($info));
        $process = $this->generateThemeFromStarterkit();
        $result = $process->run();
        $this->assertEquals('Theme generated successfully to themes/test_custom_theme', trim($process->getOutput()), $process->getErrorOutput());
        $this->assertSame(0, $result);
        $theme_path_relative = 'themes/test_custom_theme';
        $info = $this->assertThemeExists($theme_path_relative);
        self::assertArrayNotHasKey('hidden', $info);
        self::assertArrayHasKey('generator', $info);
        self::assertEquals('starterkit_theme:9.4.0', $info['generator']);
        // Confirm readme is rewritten.
        $readme_file = $this->getWorkspaceDirectory() . "/{$theme_path_relative}/README.md";
        $this->assertSame('test_custom_theme theme, generated from starterkit_theme. Additional information on generating themes can be found in the [Starterkit documentation](https://www.drupal.org/docs/core-modules-and-themes/core-themes/starterkit-theme).', file_get_contents($readme_file));
        // Ensure that the generated theme can be installed.
        $this->installQuickStart('minimal');
        $this->formLogin($this->adminUsername, $this->adminPassword);
        $this->visit('/admin/appearance');
        $this->getMink()
            ->assertSession()
            ->pageTextContains('Test custom starterkit');
        $this->getMink()
            ->assertSession()
            ->pageTextContains('Custom theme generated from a starterkit theme');
        $this->getMink()
            ->getSession()
            ->getPage()
            ->clickLink('Install "Test custom starterkit theme" theme');
        $this->getMink()
            ->assertSession()
            ->pageTextContains('The "Test custom starterkit theme" theme has been installed.');
        // Ensure that a new theme cannot be generated when the destination
        // directory already exists.
        $theme_path_absolute = $this->getWorkspaceDirectory() . "/{$theme_path_relative}";
        $this->assertFileExists($theme_path_absolute . '/test_custom_theme.theme');
        unlink($theme_path_absolute . '/test_custom_theme.theme');
        $process = $this->generateThemeFromStarterkit();
        $result = $process->run();
        $this->assertStringContainsString('Theme could not be generated because the destination directory', $process->getErrorOutput());
        $this->assertStringContainsString($theme_path_relative, $process->getErrorOutput());
        $this->assertSame(1, $result);
        $this->assertFileDoesNotExist($theme_path_absolute . '/test_custom_theme.theme');
    }
    
    /**
     * Tests generating a theme from another Starterkit enabled theme.
     */
    public function testGeneratingFromAnotherTheme() {
        // Do not rely on \Drupal::VERSION: change the version to a concrete version
        // number, to simulate using a tagged core release.
        $starterkit_info_yml = $this->getWorkspaceDirectory() . '/core/themes/starterkit_theme/starterkit_theme.info.yml';
        $info = Yaml::decode(file_get_contents($starterkit_info_yml));
        $info['version'] = '9.4.0';
        file_put_contents($starterkit_info_yml, Yaml::encode($info));
        $process = $this->generateThemeFromStarterkit();
        $exit_code = $process->run();
        $this->assertSame('Theme generated successfully to themes/test_custom_theme', trim($process->getOutput()), $process->getErrorOutput());
        $this->assertSame(0, $exit_code);
        $install_command = [
            $this->php,
            'core/scripts/drupal',
            'generate-theme',
            'generated_from_another_theme',
            '--name="Generated from another theme"',
            '--description="Custom theme generated from a theme other than starterkit_theme"',
            '--starterkit=test_custom_theme',
        ];
        $process = new Process($install_command);
        $exit_code = $process->run();
        $this->assertSame('Theme generated successfully to themes/generated_from_another_theme', trim($process->getOutput()), $process->getErrorOutput());
        $this->assertSame(0, $exit_code);
        // Confirm readme is rewritten.
        $readme_file = $this->getWorkspaceDirectory() . '/themes/generated_from_another_theme/README.md';
        $this->assertSame('generated_from_another_theme theme, generated from test_custom_theme. Additional information on generating themes can be found in the [Starterkit documentation](https://www.drupal.org/docs/core-modules-and-themes/core-themes/starterkit-theme).', file_get_contents($readme_file));
    }
    
    /**
     * Tests the generate-theme command on a dev snapshot of Drupal core.
     */
    public function testDevSnapshot() {
        // Do not rely on \Drupal::VERSION: change the version to a development
        // snapshot version number, to simulate using a branch snapshot of core.
        $starterkit_info_yml = $this->getWorkspaceDirectory() . '/core/themes/starterkit_theme/starterkit_theme.info.yml';
        $info = Yaml::decode(file_get_contents($starterkit_info_yml));
        $info['version'] = '9.4.0-dev';
        file_put_contents($starterkit_info_yml, Yaml::encode($info));
        $process = $this->generateThemeFromStarterkit();
        $result = $process->run();
        $this->assertEquals('Theme generated successfully to themes/test_custom_theme', trim($process->getOutput()), $process->getErrorOutput());
        $this->assertSame(0, $result);
        $theme_path_relative = 'themes/test_custom_theme';
        $info = $this->assertThemeExists($theme_path_relative);
        self::assertArrayNotHasKey('hidden', $info);
        self::assertArrayHasKey('generator', $info);
        self::assertMatchesRegularExpression('/^starterkit_theme\\:9.4.0-dev#[0-9a-f]+$/', $info['generator']);
    }
    
    /**
     * Tests the generate-theme command on a theme with a release version number.
     */
    public function testContribStarterkit() : void {
        // Change the version to a concrete version number, to simulate using a
        // contrib theme as the starterkit.
        $starterkit_info_yml = $this->getWorkspaceDirectory() . '/core/themes/starterkit_theme/starterkit_theme.info.yml';
        $info = Yaml::decode(file_get_contents($starterkit_info_yml));
        $info['version'] = '1.20';
        file_put_contents($starterkit_info_yml, Yaml::encode($info));
        $process = $this->generateThemeFromStarterkit();
        $result = $process->run();
        $this->assertEquals('Theme generated successfully to themes/test_custom_theme', trim($process->getOutput()), $process->getErrorOutput());
        $this->assertSame(0, $result);
        $info = $this->assertThemeExists('themes/test_custom_theme');
        self::assertArrayNotHasKey('hidden', $info);
        self::assertArrayHasKey('generator', $info);
        self::assertEquals('starterkit_theme:1.20', $info['generator']);
    }
    
    /**
     * Tests the generate-theme command on a theme with a dev version number.
     */
    public function testContribStarterkitDevSnapshot() : void {
        // Change the version to a development snapshot version number, to simulate
        // using a contrib theme as the starterkit.
        $starterkit_info_yml = $this->getWorkspaceDirectory() . '/core/themes/starterkit_theme/starterkit_theme.info.yml';
        $info = Yaml::decode(file_get_contents($starterkit_info_yml));
        $info['core_version_requirement'] = '*';
        $info['version'] = '7.x-dev';
        file_put_contents($starterkit_info_yml, Yaml::encode($info));
        // Avoid the core git commit from being considered the source theme's: move
        // it out of core.
        Process::fromShellCommandline('mv core/themes/starterkit_theme themes/', $this->getWorkspaceDirectory())
            ->run();
        $process = $this->generateThemeFromStarterkit();
        $result = $process->run();
        $this->assertEquals("The source theme starterkit_theme has a development version number (7.x-dev). Because it is not a git checkout, a specific commit could not be identified. This makes tracking changes in the source theme difficult. Are you sure you want to continue? (yes/no) [yes]:\n > Theme generated successfully to themes/test_custom_theme", trim($process->getOutput()), $process->getErrorOutput());
        $this->assertSame(0, $result);
        $info = $this->assertThemeExists('themes/test_custom_theme');
        self::assertArrayNotHasKey('hidden', $info);
        self::assertArrayHasKey('generator', $info);
        self::assertEquals('starterkit_theme:7.x-dev#unknown-commit', $info['generator']);
    }
    
    /**
     * Tests the generate-theme command on a theme with a dev version without git.
     */
    public function testContribStarterkitDevSnapshotWithGitNotInstalled() : void {
        // Change the version to a development snapshot version number, to simulate
        // using a contrib theme as the starterkit.
        $starterkit_info_yml = $this->getWorkspaceDirectory() . '/core/themes/starterkit_theme/starterkit_theme.info.yml';
        $info = Yaml::decode(file_get_contents($starterkit_info_yml));
        $info['core_version_requirement'] = '*';
        $info['version'] = '7.x-dev';
        file_put_contents($starterkit_info_yml, Yaml::encode($info));
        // Avoid the core git commit from being considered the source theme's: move
        // it out of core.
        Process::fromShellCommandline('mv core/themes/starterkit_theme themes/', $this->getWorkspaceDirectory())
            ->run();
        // Confirm that 'git' is available.
        $output = [];
        exec('git --help', $output, $status);
        $this->assertEquals(0, $status);
        // Modify our $PATH so that it begins with a path that contains an
        // executable script named 'git' that always exits with 127, as if git were
        // not found. Note that we run our tests using process isolation, so we do
        // not need to restore the PATH when we are done.
        $unavailableGitPath = $this->getWorkspaceDirectory() . '/bin';
        mkdir($unavailableGitPath);
        $bash = <<<SH
#!/bin/bash
exit 127

SH;
        file_put_contents($unavailableGitPath . '/git', $bash);
        chmod($unavailableGitPath . '/git', 0755);
        // Confirm that 'git' is no longer available.
        $env = [
            'PATH' => $unavailableGitPath . ':' . getenv('PATH'),
            'COLUMNS' => 80,
        ];
        $process = new Process([
            'git',
            '--help',
        ], NULL, $env);
        $process->run();
        $this->assertEquals(127, $process->getExitCode(), 'Fake git used by process.');
        $process = $this->generateThemeFromStarterkit($env);
        $result = $process->run();
        $this->assertEquals("[ERROR] The source theme starterkit_theme has a development version number     \n         (7.x-dev). Determining a specific commit is not possible because git is\n         not installed. Either install git or use a tagged release to generate a\n         theme.", trim($process->getOutput()), $process->getErrorOutput());
        $this->assertSame(1, $result);
        $this->assertFileDoesNotExist($this->getWorkspaceDirectory() . "/themes/test_custom_theme");
    }
    
    /**
     * Tests the generate-theme command on a theme without a version number.
     */
    public function testCustomStarterkit() : void {
        // Omit the version, to simulate using a custom theme as the starterkit.
        $starterkit_info_yml = $this->getWorkspaceDirectory() . '/core/themes/starterkit_theme/starterkit_theme.info.yml';
        $info = Yaml::decode(file_get_contents($starterkit_info_yml));
        unset($info['version']);
        file_put_contents($starterkit_info_yml, Yaml::encode($info));
        $process = $this->generateThemeFromStarterkit();
        $result = $process->run();
        $this->assertEquals("The source theme starterkit_theme does not have a version specified. This makes tracking changes in the source theme difficult. Are you sure you want to continue? (yes/no) [yes]:\n > Theme generated successfully to themes/test_custom_theme", trim($process->getOutput()), $process->getErrorOutput());
        $this->assertSame(0, $result);
        $info = $this->assertThemeExists('themes/test_custom_theme');
        self::assertArrayNotHasKey('hidden', $info);
        self::assertArrayHasKey('generator', $info);
        self::assertEquals('starterkit_theme:unknown-version', $info['generator']);
    }
    
    /**
     * Tests themes that do not exist return an error.
     */
    public function testThemeDoesNotExist() : void {
        $install_command = [
            $this->php,
            'core/scripts/drupal',
            'generate-theme',
            'test_custom_theme',
            '--name="Test custom starterkit theme"',
            '--description="Custom theme generated from a starterkit theme"',
            '--starterkit',
            'foobarbaz',
        ];
        $process = new Process($install_command, NULL);
        $process->setTimeout(60);
        $result = $process->run();
        $this->assertStringContainsString('Theme source theme foobarbaz cannot be found.', trim($process->getErrorOutput()));
        $this->assertSame(1, $result);
    }
    
    /**
     * Tests that only themes with `starterkit` flag can be used.
     */
    public function testStarterKitFlag() : void {
        // Explicitly not a starter theme.
        $install_command = [
            $this->php,
            'core/scripts/drupal',
            'generate-theme',
            'test_custom_theme',
            '--name="Test custom starterkit theme"',
            '--description="Custom theme generated from a starterkit theme"',
            '--starterkit',
            'stark',
        ];
        $process = new Process($install_command, NULL);
        $process->setTimeout(60);
        $result = $process->run();
        $this->assertStringContainsString('Theme source theme stark is not a valid starter kit.', trim($process->getErrorOutput()));
        $this->assertSame(1, $result);
        // Has not defined `starterkit`.
        $install_command = [
            $this->php,
            'core/scripts/drupal',
            'generate-theme',
            'test_custom_theme',
            '--name="Test custom starterkit theme"',
            '--description="Custom theme generated from a starterkit theme"',
            '--starterkit',
            'olivero',
        ];
        $process = new Process($install_command, NULL);
        $process->setTimeout(60);
        $result = $process->run();
        $this->assertStringContainsString('Theme source theme olivero is not a valid starter kit.', trim($process->getErrorOutput()));
        $this->assertSame(1, $result);
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
BuildTestBase::$commandProcess private property The most recent command process.
BuildTestBase::$destroyBuild protected property Default to destroying build artifacts after a test finishes.
BuildTestBase::$hostName private static property Our native host name, used by PHP when it starts up the server.
BuildTestBase::$hostPort private property Port that will be tested.
BuildTestBase::$mink private property The Mink session manager.
BuildTestBase::$phpFinder private property The PHP executable finder.
BuildTestBase::$portLocks private property A list of ports used by the test.
BuildTestBase::$serverDocroot private property The docroot for the server process.
BuildTestBase::$serverProcess private property The process that&#039;s running the HTTP server.
BuildTestBase::$workspaceDir private property The working directory where this test will manipulate files.
BuildTestBase::assertCommandExitCode public function Asserts that the last command returned the specified exit code.
BuildTestBase::assertCommandOutputContains public function Assert that text is present in the output of the most recent command.
BuildTestBase::assertCommandSuccessful public function Asserts that the last command ran without error.
BuildTestBase::assertDrupalVisit public function Helper function to assert that the last visit was a Drupal site.
BuildTestBase::assertErrorOutputContains public function Assert that text is present in the error output of the most recent command.
BuildTestBase::assertErrorOutputNotContains public function Assert text is not present in the error output of the most recent command.
BuildTestBase::checkPortIsAvailable protected function Checks whether a port is available.
BuildTestBase::copyCodebase public function Copy the current working codebase into a workspace.
BuildTestBase::executeCommand public function Run a command.
BuildTestBase::findAvailablePort protected function Discover an available port number.
BuildTestBase::getCodebaseFinder public function Get a default Finder object for a Drupal codebase.
BuildTestBase::getComposerRoot public function Gets the path to the Composer root directory.
BuildTestBase::getDrupalRoot public function Get the root path of this Drupal codebase.
BuildTestBase::getDrupalRootStatic public static function Get the root path of this Drupal codebase.
BuildTestBase::getMink public function Get the Mink instance.
BuildTestBase::getPortNumber protected function Get the port number for requests.
BuildTestBase::getWorkingPath protected function Get the working directory within the workspace, creating if necessary.
BuildTestBase::getWorkingPathDrupalRoot public function Gets the working path for Drupal core.
BuildTestBase::getWorkspaceDirectory public function Full path to the workspace where this test can build.
BuildTestBase::getWorkspaceDrupalRoot public function Gets the path to Drupal root in the workspace directory.
BuildTestBase::initMink protected function Set up the Mink session manager.
BuildTestBase::instantiateServer protected function Do the work of making a server process.
BuildTestBase::standUpServer protected function Makes a local test server using PHP&#039;s internal HTTP server.
BuildTestBase::stopServer protected function Stop the HTTP server, zero out all necessary variables.
BuildTestBase::tearDown protected function 1
BuildTestBase::visit public function Visit a URI on the HTTP server.
GenerateThemeTest::$php protected property The PHP executable path.
GenerateThemeTest::assertThemeExists private function Asserts the theme exists. Returns the parsed *.info.yml file.
GenerateThemeTest::generateThemeFromStarterkit private function Generates PHP process to generate a theme from core's starterkit theme.
GenerateThemeTest::setUp protected function Overrides BuildTestBase::setUp
GenerateThemeTest::test public function Tests the generate-theme command.
GenerateThemeTest::testContribStarterkit public function Tests the generate-theme command on a theme with a release version number.
GenerateThemeTest::testContribStarterkitDevSnapshot public function Tests the generate-theme command on a theme with a dev version number.
GenerateThemeTest::testContribStarterkitDevSnapshotWithGitNotInstalled public function Tests the generate-theme command on a theme with a dev version without git.
GenerateThemeTest::testCustomStarterkit public function Tests the generate-theme command on a theme without a version number.
GenerateThemeTest::testDevSnapshot public function Tests the generate-theme command on a dev snapshot of Drupal core.
GenerateThemeTest::testGeneratingFromAnotherTheme public function Tests generating a theme from another Starterkit enabled theme.
GenerateThemeTest::testStarterKitFlag public function Tests that only themes with `starterkit` flag can be used.
GenerateThemeTest::testThemeDoesNotExist public function Tests themes that do not exist return an error.
PhpUnitWarnings::$deprecationWarnings private static property Deprecation warnings from PHPUnit to raise with @trigger_error().
PhpUnitWarnings::addWarning public function Converts PHPUnit deprecation warnings to E_USER_DEPRECATED.
QuickStartTestBase::$adminPassword protected property Password of the admin account generated during install.
QuickStartTestBase::$adminUsername protected property User name of the admin account generated during install.
QuickStartTestBase::formLogin public function Helper that uses Drupal&#039;s user/login form to log in.
QuickStartTestBase::installQuickStart public function Install a Drupal site using the quick start feature.
RequiresComposerTrait::requiresComposer public static function @beforeClass

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.