From 868d558129ba2e5f385279ade46a4da3dc673fa1 Mon Sep 17 00:00:00 2001 From: Huzaifa Al Mesbah Date: Tue, 20 Oct 2026 08:40:27 +0600 Subject: [PATCH] Tests: Add unit test coverage for _get_plugin_data_markup_translate(). --- .../plugin/getPluginDataMarkupTranslate.php | 414 ++++++++++++++++++ 1 file changed, 414 insertions(+) create mode 100644 tests/phpunit/tests/admin/includes/plugin/getPluginDataMarkupTranslate.php diff --git a/tests/phpunit/tests/admin/includes/plugin/getPluginDataMarkupTranslate.php b/tests/phpunit/tests/admin/includes/plugin/getPluginDataMarkupTranslate.php new file mode 100644 index 0000000000000..2e36529ceb171 --- /dev/null +++ b/tests/phpunit/tests/admin/includes/plugin/getPluginDataMarkupTranslate.php @@ -0,0 +1,414 @@ +original_registry = $GLOBALS['wp_textdomain_registry']; + $this->original_l10n = $GLOBALS['l10n']; + } + + /** + * Restores the text domain registry and the loaded translations. + */ + public function tear_down() { + $GLOBALS['wp_textdomain_registry'] = $this->original_registry; + $GLOBALS['l10n'] = $this->original_l10n; + + parent::tear_down(); + } + + /** + * Tests that the fields are sanitized without markup. + * + * @ticket 65819 + */ + public function test_should_sanitize_fields_without_markup() { + $plugin_data = $this->get_plugin_data( + array( + 'Name' => 'My Plugin Link', + 'PluginURI' => 'javascript:alert(1)', + 'Version' => '1.0 beta bold', + 'Description' => 'A fine plugin cited & "quoted".', + 'Author' => 'Jane & Co', + 'AuthorURI' => 'https://example.org/author?a=1&b="2"', + ) + ); + + $this->assertSame( + array( + 'Name' => 'My Plugin Link', + 'PluginURI' => '', + 'Version' => '1.0 beta bold', + 'Description' => 'A fine plugin cited & "quoted".', + 'Author' => 'Jane & Co', + 'AuthorURI' => 'https://example.org/author?a=1&b=2', + 'TextDomain' => '', + 'DomainPath' => '', + 'Title' => 'My Plugin Link', + 'AuthorName' => 'Jane & Co', + ), + _get_plugin_data_markup_translate( WP_PLUGIN_DIR . '/my-plugin/my-plugin.php', $plugin_data, false, false ) + ); + } + + /** + * Tests that markup links the name and author and credits the author. + * + * @ticket 65819 + */ + public function test_should_add_markup() { + $plugin_data = $this->get_plugin_data( + array( + 'Name' => 'My Plugin', + 'PluginURI' => 'https://example.org/plugin/', + 'Description' => "It's a plugin -- really.", + 'Author' => 'Jane', + 'AuthorURI' => 'https://example.org/jane/', + ) + ); + + $result = _get_plugin_data_markup_translate( WP_PLUGIN_DIR . '/my-plugin/my-plugin.php', $plugin_data, true, false ); + + $this->assertSame( 'My Plugin', $result['Title'], 'The title should link to the plugin URI.' ); + $this->assertSame( 'Jane', $result['Author'], 'The author should link to the author URI.' ); + $this->assertSame( 'Jane', $result['AuthorName'], 'The author name should not be linked.' ); + $this->assertSame( 'My Plugin', $result['Name'], 'The name should not be linked.' ); + $this->assertSame( + 'It’s a plugin — really. By Jane.', + $result['Description'], + 'The description should be texturized and credit the author.' + ); + } + + /** + * Tests that markup leaves out links and the author credit when their fields are empty. + * + * @ticket 65819 + * + * @dataProvider data_empty_markup_fields + * + * @param array $fields The plugin data fields. + * @param array $expected The expected Title, Author and Description. + */ + public function test_should_skip_markup_for_empty_fields( $fields, $expected ) { + $result = _get_plugin_data_markup_translate( WP_PLUGIN_DIR . '/my-plugin/my-plugin.php', $this->get_plugin_data( $fields ), true, false ); + + $this->assertSame( + $expected, + array( + 'Title' => $result['Title'], + 'Author' => $result['Author'], + 'Description' => $result['Description'], + ) + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public static function data_empty_markup_fields() { + return array( + 'no plugin URI' => array( + array( + 'Name' => 'My Plugin', + 'Description' => 'Text.', + 'Author' => 'Jane', + ), + array( + 'Title' => 'My Plugin', + 'Author' => 'Jane', + 'Description' => 'Text. By Jane.', + ), + ), + 'no name' => array( + array( + 'PluginURI' => 'https://example.org/plugin/', + 'Description' => 'Text.', + ), + array( + 'Title' => '', + 'Author' => '', + 'Description' => 'Text.', + ), + ), + 'no author URI' => array( + array( + 'Name' => 'My Plugin', + 'Author' => 'Jane', + ), + array( + 'Title' => 'My Plugin', + 'Author' => 'Jane', + 'Description' => ' By Jane.', + ), + ), + 'author "0"' => array( + array( + 'Name' => 'My Plugin', + 'Author' => '0', + 'AuthorURI' => 'https://example.org/jane/', + ), + array( + 'Title' => 'My Plugin', + 'Author' => '0', + 'Description' => '', + ), + ), + ); + } + + /** + * Tests that the translatable fields are translated with the plugin's text domain. + * + * @ticket 65819 + */ + public function test_should_translate_fields_with_text_domain() { + $GLOBALS['l10n']['my-plugin'] = new MO(); + + add_filter( 'gettext', array( $this, 'translate_string' ), 10, 3 ); + + $result = _get_plugin_data_markup_translate( + WP_PLUGIN_DIR . '/my-plugin/my-plugin.php', + $this->get_plugin_data( + array( + 'Name' => 'My Plugin', + 'PluginURI' => 'https://example.org/plugin/', + 'Version' => '1.0', + 'Description' => 'Text.', + 'Author' => 'Jane', + 'TextDomain' => 'my-plugin', + ) + ), + false, + true + ); + + $this->assertSame( + array( + array( 'My Plugin', 'my-plugin' ), + array( 'https://example.org/plugin/', 'my-plugin' ), + array( 'Text.', 'my-plugin' ), + array( 'Jane', 'my-plugin' ), + array( '1.0', 'my-plugin' ), + ), + $this->translated, + 'Only the non-empty fields should be translated, with the plugin text domain.' + ); + $this->assertSame( '[My Plugin]', $result['Name'], 'The name should be translated.' ); + $this->assertSame( 'https://example.org/plugin/de/', $result['PluginURI'], 'The plugin URI should be translated.' ); + $this->assertSame( '[Text.]', $result['Description'], 'The description should be translated.' ); + $this->assertSame( '[Jane]', $result['Author'], 'The author should be translated.' ); + $this->assertSame( '[1.0]', $result['Version'], 'The version should be translated.' ); + } + + /** + * Tests that the fields are not translated when translation is disabled. + * + * @ticket 65819 + */ + public function test_should_not_translate_when_disabled() { + add_filter( 'gettext', array( $this, 'translate_string' ), 10, 3 ); + + $result = _get_plugin_data_markup_translate( + WP_PLUGIN_DIR . '/hello.php', + $this->get_plugin_data( + array( + 'Name' => 'My Plugin', + 'TextDomain' => 'my-plugin', + ) + ), + false, + false + ); + + $this->assertSame( array(), $this->translated, 'No field should be translated.' ); + $this->assertSame( 'My Plugin', $result['Name'], 'The name should not change.' ); + } + + /** + * Tests the text domain used for a plugin without one. + * + * @ticket 65819 + * + * @dataProvider data_plugins_without_text_domain + * + * @param string $plugin_file The plugin file, relative to the plugins directory. + * @param array $expected The expected translate() calls. + */ + public function test_should_handle_plugin_without_text_domain( $plugin_file, $expected ) { + add_filter( 'gettext', array( $this, 'translate_string' ), 10, 3 ); + + _get_plugin_data_markup_translate( + WP_PLUGIN_DIR . '/' . $plugin_file, + $this->get_plugin_data( array( 'Name' => 'My Plugin' ) ), + false, + true + ); + + $this->assertSame( $expected, $this->translated ); + } + + /** + * Data provider. + * + * @return array[] + */ + public static function data_plugins_without_text_domain() { + return array( + 'Hello Dolly uses the default text domain' => array( + 'hello.php', + array( array( 'My Plugin', 'default' ) ), + ), + 'Hello Dolly in a plugin directory' => array( + 'hello-dolly/hello.php', + array( array( 'My Plugin', 'default' ) ), + ), + 'other plugins are not translated' => array( + 'my-plugin/my-plugin.php', + array(), + ), + ); + } + + /** + * Tests that the plugin text domain is loaded from the plugin's directory. + * + * @ticket 65819 + * + * @dataProvider data_text_domain_paths + * + * @param string $domain_path The Domain Path header. + * @param string $expected_path The expected path, relative to the plugins directory. + */ + public function test_should_load_text_domain_from_plugin_directory( $domain_path, $expected_path ) { + $registry = $this->createMock( 'WP_Textdomain_Registry' ); + $registry->expects( $this->once() ) + ->method( 'set_custom_path' ) + ->with( 'my-plugin', WP_PLUGIN_DIR . '/' . $expected_path ); + + $GLOBALS['wp_textdomain_registry'] = $registry; + + _get_plugin_data_markup_translate( + WP_PLUGIN_DIR . '/my-plugin/my-plugin.php', + $this->get_plugin_data( + array( + 'TextDomain' => 'my-plugin', + 'DomainPath' => $domain_path, + ) + ), + false, + true + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public static function data_text_domain_paths() { + return array( + 'no domain path' => array( '', 'my-plugin' ), + 'domain path' => array( '/languages', 'my-plugin/languages' ), + 'zero domain path' => array( '0', 'my-plugin' ), + ); + } + + /** + * Tests that a text domain that is already loaded is not loaded again. + * + * @ticket 65819 + */ + public function test_should_not_reload_loaded_text_domain() { + $registry = $this->createMock( 'WP_Textdomain_Registry' ); + $registry->expects( $this->never() )->method( 'set_custom_path' ); + + $GLOBALS['wp_textdomain_registry'] = $registry; + $GLOBALS['l10n']['my-plugin'] = new MO(); + + _get_plugin_data_markup_translate( + WP_PLUGIN_DIR . '/my-plugin/my-plugin.php', + $this->get_plugin_data( array( 'TextDomain' => 'my-plugin' ) ), + false, + true + ); + } + + /** + * Returns plugin data with all fields the function reads. + * + * @param array $fields The fields to set. + * @return array The plugin data. + */ + private function get_plugin_data( $fields ) { + return array_merge( + array( + 'Name' => '', + 'PluginURI' => '', + 'Version' => '', + 'Description' => '', + 'Author' => '', + 'AuthorURI' => '', + 'TextDomain' => '', + 'DomainPath' => '', + ), + $fields + ); + } + + /** + * Records strings passed to translate() and returns a translation. + * + * URLs get a language path, other strings are wrapped in brackets. + * + * @param string $translation The translated text. + * @param string $text The text to translate. + * @param string $domain The text domain. + * @return string The translation. + */ + public function translate_string( $translation, $text, $domain ) { + $this->translated[] = array( $text, $domain ); + + if ( str_starts_with( $text, 'https://' ) ) { + return $text . 'de/'; + } + + return '[' . $text . ']'; + } +}