JezK
Edit File: test-plugin-headers.php
<?php /** * Test Plugin Readme and PHP Headers. Adapted from @peterwilsoncc's plugin template. * * @link https://ewww.io * @package Ewww_Image_Optimizer */ /** * Test Plugin Readme and PHP Headers. */ class EWWWIO_Test_Plugin_Headers extends WP_UnitTestCase { const OPTIONAL = 0; const REQUIRED = 1; const FORBIDDEN = 2; /** * Readme headers specification. * * @var array<string,int> Headers defined in the readme spec. Key: Header; Value: OPTIONAL, REQUIRED, FORBIDDEN. */ public static $readme_headers = array( 'Contributors' => self::REQUIRED, 'Tags' => self::OPTIONAL, 'Donate link' => self::OPTIONAL, 'Tested up to' => self::REQUIRED, 'Stable tag' => self::REQUIRED, 'License' => self::REQUIRED, 'License URI' => self::OPTIONAL, // Plugin file headers that do not belong in the readme. 'Plugin Name' => self::FORBIDDEN, 'Plugin URI' => self::FORBIDDEN, 'Description' => self::FORBIDDEN, 'Version' => self::FORBIDDEN, 'Author' => self::FORBIDDEN, 'Author URI' => self::FORBIDDEN, 'Text Domain' => self::FORBIDDEN, 'Domain Path' => self::FORBIDDEN, 'Network' => self::FORBIDDEN, 'Update URI' => self::FORBIDDEN, 'Requires at least' => self::FORBIDDEN, // Both WP and the plugin directory prefer the version in the plugin file. 'Requires PHP' => self::FORBIDDEN, // Both WP and the plugin directory prefer the version in the plugin file. 'Requires Plugins' => self::FORBIDDEN, ); /** * Plugin headers specification * * @var array<string,int> Headers defined in the plugin spec. Key: Header; Value: OPTIONAL, REQUIRED, FORBIDDEN. */ public static $plugin_headers = array( 'Plugin Name' => self::REQUIRED, 'Plugin URI' => self::OPTIONAL, 'Description' => self::REQUIRED, 'Version' => self::REQUIRED, 'Requires at least' => self::REQUIRED, // Not required by the spec but I'm enforcing it. 'Requires PHP' => self::REQUIRED, // Not required by the spec but I'm enforcing it. 'Author' => self::REQUIRED, 'Author URI' => self::OPTIONAL, 'License' => self::REQUIRED, 'License URI' => self::OPTIONAL, 'Text Domain' => self::OPTIONAL, 'Domain Path' => self::OPTIONAL, 'Network' => self::OPTIONAL, 'Update URI' => self::OPTIONAL, 'Requires Plugins' => self::OPTIONAL, // Readme file headers that do not belong in the plugin file. 'Contributors' => self::FORBIDDEN, 'Tags' => self::FORBIDDEN, 'Donate link' => self::FORBIDDEN, 'Stable tag' => self::FORBIDDEN, /* * Opinionated: Allowed by the spec. * * The WordPress plugin directory will use the plugin file headers if * it exists, and fall back to the readme file if it does not. * * However, the 10up Github Action for deploying updates to the * directory will require a version bump if the plugin file is * modified, so it's best to keep tested up to in the readme file. * * WordPress Core doesn't use the header, it pulls the data in * from the plugin API. */ 'Tested up to' => self::FORBIDDEN, ); /** * Headers defined in the plugins readme.text file. * * @var string[] Headers defined in the readme spec Header => value. */ public static $defined_readme_headers = array(); /** * Headers defined in the plugin file. * * @var string[] Headers defined in the plugin spec Header => value. */ public static $defined_plugin_headers = array(); /** * Set up shared fixtures. */ public static function wpSetupBeforeClass() { // Get the readme headers. $readme_file_data = array(); foreach ( self::$readme_headers as $header => $required ) { $readme_file_data[ $header ] = $header; } self::$defined_readme_headers = get_file_data( __DIR__ . '/../readme.txt', $readme_file_data ); self::$defined_readme_headers = array_filter( self::$defined_readme_headers ); // Get the plugin headers. // Plugin name. $plugin_file_name = basename( dirname( __DIR__ ) ) . '.php'; if ( ! file_exists( __DIR__ . "/../{$plugin_file_name}" ) ) { // Fallback to the generic plugin file name. $plugin_file_name = 'plugin.php'; } $plugin_file_data = array(); foreach ( self::$plugin_headers as $header => $required ) { $plugin_file_data[ $header ] = $header; } self::$defined_plugin_headers = get_file_data( __DIR__ . "/../{$plugin_file_name}", $plugin_file_data ); self::$defined_plugin_headers = array_filter( self::$defined_plugin_headers ); } /** * Test that the readme file has all required headers. * * @dataProvider data_required_readme_headers * * @param string $header Header to test. */ public function test_required_readme_headers( $header ) { $this->assertArrayHasKey( $header, self::$defined_readme_headers, "The readme file header '{$header}' is missing." ); $this->assertNotEmpty( self::$defined_readme_headers[ $header ], "The readme file header '{$header}' is empty." ); } /** * Data provider for test_required_readme_headers. * * @return array[] Data provider. */ public function data_required_readme_headers() { $required_headers = array_filter( self::$readme_headers, function ( $status ) { return self::REQUIRED === $status; } ); $headers = array(); foreach ( $required_headers as $header => $required ) { $headers[ $header ] = array( $header ); } return $headers; } /** * Test that the readme file does not have any forbidden headers. * * @dataProvider data_forbidden_readme_headers * * @param string $header Header to test. */ public function test_forbidden_readme_headers( $header ) { $this->assertArrayNotHasKey( $header, self::$defined_readme_headers, "The readme file header '{$header}' is forbidden." ); } /** * Data provider for test_forbidden_readme_headers. * * @return array[] Data provider. */ public function data_forbidden_readme_headers() { $forbidden_headers = array_filter( self::$readme_headers, function ( $status ) { return self::FORBIDDEN === $status; } ); $headers = array(); foreach ( $forbidden_headers as $header => $required ) { $headers[ $header ] = array( $header ); } return $headers; } /** * Test that the plugin file has all required headers. * * @dataProvider data_required_plugin_headers * * @param string $header Header to test. */ public function test_required_plugin_headers( $header ) { $this->assertArrayHasKey( $header, self::$defined_plugin_headers, "The plugin file header '{$header}' is missing." ); $this->assertNotEmpty( self::$defined_plugin_headers[ $header ], "The readme file header '{$header}' is empty." ); } /** * Data provider for test_required_plugin_headers. * * @return array[] Data provider. */ public function data_required_plugin_headers() { $required_headers = array_filter( self::$plugin_headers, function ( $status ) { return self::REQUIRED === $status; } ); $headers = array(); foreach ( $required_headers as $header => $required ) { $headers[ $header ] = array( $header ); } return $headers; } /** * Test that the plugin file does not have any forbidden headers. * * @dataProvider data_forbidden_plugin_headers * * @param string $header Header to test. */ public function test_forbidden_plugin_headers( $header ) { $this->assertArrayNotHasKey( $header, self::$defined_plugin_headers, "The plugin file header '{$header}' is forbidden." ); } /** * Data provider for test_forbidden_plugin_headers. * * @return array[] Data provider. */ public function data_forbidden_plugin_headers() { $forbidden_headers = array_filter( self::$plugin_headers, function ( $status ) { return self::FORBIDDEN === $status; } ); $headers = array(); foreach ( $forbidden_headers as $header => $required ) { $headers[ $header ] = array( $header ); } return $headers; } }