Improve SportsPress Open Graph images
This commit is contained in:
222
tests/test-featured-image-generator.php
Normal file
222
tests/test-featured-image-generator.php
Normal file
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for the SportsPress event image generator.
|
||||
*
|
||||
* @package Tonys_Sportspress_Enhancements
|
||||
*/
|
||||
|
||||
/**
|
||||
* Featured image generator tests.
|
||||
*/
|
||||
class Test_Featured_Image_Generator extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Temp files created during a test.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $temp_files = array();
|
||||
|
||||
/**
|
||||
* Clean up temp files.
|
||||
*/
|
||||
public function tear_down(): void {
|
||||
foreach ( $this->temp_files as $file ) {
|
||||
if ( file_exists( $file ) ) {
|
||||
unlink( $file );
|
||||
}
|
||||
}
|
||||
|
||||
$this->temp_files = array();
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a post.
|
||||
*
|
||||
* @param string $type Post type.
|
||||
* @param string $title Title.
|
||||
* @return int
|
||||
*/
|
||||
private function create_post_of_type( $type, $title ) {
|
||||
return self::factory()->post->create(
|
||||
array(
|
||||
'post_type' => $type,
|
||||
'post_title' => $title,
|
||||
'post_status' => 'publish',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a small raster fixture.
|
||||
*
|
||||
* @param string $extension File extension.
|
||||
* @return string
|
||||
*/
|
||||
private function create_raster_fixture( $extension ) {
|
||||
$image = imagecreatetruecolor( 24, 24 );
|
||||
$red = imagecolorallocate( $image, 200, 0, 0 );
|
||||
imagefilledrectangle( $image, 0, 0, 23, 23, $red );
|
||||
|
||||
$file = tempnam( sys_get_temp_dir(), 'sp-img-' );
|
||||
$path = $file . '.' . $extension;
|
||||
rename( $file, $path );
|
||||
|
||||
switch ( $extension ) {
|
||||
case 'jpg':
|
||||
imagejpeg( $image, $path );
|
||||
break;
|
||||
case 'gif':
|
||||
imagegif( $image, $path );
|
||||
break;
|
||||
case 'webp':
|
||||
imagewebp( $image, $path );
|
||||
break;
|
||||
case 'png':
|
||||
default:
|
||||
imagepng( $image, $path );
|
||||
break;
|
||||
}
|
||||
|
||||
asc_sp_event_image_destroy( $image );
|
||||
|
||||
$this->temp_files[] = $path;
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalid IDs and non-event posts produce request errors.
|
||||
*/
|
||||
public function test_invalid_and_non_event_requests_prepare_404_errors() {
|
||||
$this->assertWPError( asc_sp_event_prepare_image_request( 999999 ) );
|
||||
|
||||
$post_id = $this->create_post_of_type( 'post', 'Regular Post' );
|
||||
$error = asc_sp_event_prepare_image_request( $post_id );
|
||||
|
||||
$this->assertWPError( $error );
|
||||
$this->assertSame( 'invalid_event', $error->get_error_code() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Missing team logo paths fall back to generated text and valid dimensions.
|
||||
*/
|
||||
public function test_missing_logo_path_generates_png_with_expected_dimensions() {
|
||||
$image_data = generate_bisected_image( '#123456', '#abcdef', '/missing-left.png', '/missing-right.png', 'Hawks', 'Electrons' );
|
||||
$image = imagecreatefromstring( $image_data );
|
||||
|
||||
$this->assertNotFalse( $image );
|
||||
$this->assertSame( 1200, imagesx( $image ) );
|
||||
$this->assertSame( 628, imagesy( $image ) );
|
||||
|
||||
asc_sp_event_image_destroy( $image );
|
||||
}
|
||||
|
||||
/**
|
||||
* Square image variant generates square PNG dimensions.
|
||||
*/
|
||||
public function test_square_variant_generates_expected_dimensions() {
|
||||
$dimensions = asc_sp_event_image_variant_dimensions( 'square' );
|
||||
$image_data = generate_bisected_image( '#123456', '#abcdef', '/missing-left.png', '/missing-right.png', 'Hawks', 'Electrons', $dimensions['width'], $dimensions['height'] );
|
||||
$image = imagecreatefromstring( $image_data );
|
||||
|
||||
$this->assertNotFalse( $image );
|
||||
$this->assertSame( 1200, imagesx( $image ) );
|
||||
$this->assertSame( 1200, imagesy( $image ) );
|
||||
|
||||
asc_sp_event_image_destroy( $image );
|
||||
}
|
||||
|
||||
/**
|
||||
* Raster loader supports common GD-backed formats.
|
||||
*/
|
||||
public function test_raster_loader_supports_common_formats_when_available() {
|
||||
$formats = array(
|
||||
'png' => 'imagecreatefrompng',
|
||||
'jpg' => 'imagecreatefromjpeg',
|
||||
'gif' => 'imagecreatefromgif',
|
||||
);
|
||||
|
||||
if ( function_exists( 'imagewebp' ) && function_exists( 'imagecreatefromwebp' ) ) {
|
||||
$formats['webp'] = 'imagecreatefromwebp';
|
||||
}
|
||||
|
||||
foreach ( $formats as $extension => $function ) {
|
||||
if ( ! function_exists( $function ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $this->create_raster_fixture( $extension );
|
||||
$image = asc_sp_event_image_create_from_file( $path );
|
||||
|
||||
$this->assertNotFalse( $image, "Failed loading {$extension}" );
|
||||
$this->assertSame( 24, imagesx( $image ) );
|
||||
$this->assertSame( 24, imagesy( $image ) );
|
||||
asc_sp_event_image_destroy( $image );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundled sporty font is available for fallback text.
|
||||
*/
|
||||
public function test_bundled_bebas_neue_font_is_available() {
|
||||
$this->assertFileExists( asc_sp_event_image_font_path() );
|
||||
$this->assertIsReadable( asc_sp_event_image_font_path() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepared event request includes fallback text for missing logos.
|
||||
*/
|
||||
public function test_prepare_image_request_uses_team_short_name_fallbacks() {
|
||||
$team1 = $this->create_post_of_type( 'sp_team', 'Hawks' );
|
||||
$team2 = $this->create_post_of_type( 'sp_team', 'Electrons' );
|
||||
$event = $this->create_post_of_type( 'sp_event', 'Hawks vs Electrons' );
|
||||
|
||||
add_post_meta( $event, 'sp_team', $team1 );
|
||||
add_post_meta( $event, 'sp_team', $team2 );
|
||||
|
||||
$request = asc_sp_event_prepare_image_request( $event );
|
||||
|
||||
$this->assertIsArray( $request );
|
||||
$this->assertSame( 'Hawks', $request['team1_fallback'] );
|
||||
$this->assertSame( 'Electrons', $request['team2_fallback'] );
|
||||
$this->assertSame( '', $request['team1_logo'] );
|
||||
$this->assertSame( '', $request['team2_logo'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalid colors are safely normalized.
|
||||
*/
|
||||
public function test_invalid_colors_fall_back_to_configured_defaults() {
|
||||
$this->assertSame( '#4B5563', asc_sp_event_image_color( 'not-a-color' ) );
|
||||
$this->assertSame( '#6B7280', asc_sp_event_image_color( 'not-a-color', '#6B7280' ) );
|
||||
$this->assertSame( '#112233', asc_sp_event_image_color( '#112233' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Image cache keys include the generator version and style hash.
|
||||
*/
|
||||
public function test_prepare_image_request_uses_versioned_style_cache_key() {
|
||||
$team1 = $this->create_post_of_type( 'sp_team', 'Hawks' );
|
||||
$team2 = $this->create_post_of_type( 'sp_team', 'Electrons' );
|
||||
$event = $this->create_post_of_type( 'sp_event', 'Hawks vs Electrons' );
|
||||
|
||||
add_post_meta( $event, 'sp_team', $team1 );
|
||||
add_post_meta( $event, 'sp_team', $team2 );
|
||||
|
||||
$request = asc_sp_event_prepare_image_request( $event );
|
||||
|
||||
$this->assertStringStartsWith( 'team_image_v' . ASC_SP_EVENT_IMAGE_CACHE_VERSION . '_' . asc_sp_event_image_cache_style_hash(), $request['cache_key'] );
|
||||
$this->assertSame( 'wide', $request['variant'] );
|
||||
$this->assertSame( 1200, $request['width'] );
|
||||
$this->assertSame( 628, $request['height'] );
|
||||
|
||||
$square_request = asc_sp_event_prepare_image_request( $event, 'square' );
|
||||
|
||||
$this->assertStringContainsString( '_square_', $square_request['cache_key'] );
|
||||
$this->assertSame( 'square', $square_request['variant'] );
|
||||
$this->assertSame( 1200, $square_request['width'] );
|
||||
$this->assertSame( 1200, $square_request['height'] );
|
||||
}
|
||||
}
|
||||
271
tests/test-open-graph-tags.php
Normal file
271
tests/test-open-graph-tags.php
Normal file
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests for SportsPress event Open Graph output.
|
||||
*
|
||||
* @package Tonys_Sportspress_Enhancements
|
||||
*/
|
||||
|
||||
if ( ! class_exists( 'SP_Event' ) ) {
|
||||
/**
|
||||
* Minimal SportsPress event test double.
|
||||
*/
|
||||
class SP_Event {
|
||||
/**
|
||||
* Event post ID.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* Status values by event ID.
|
||||
*
|
||||
* @var array<int,string>
|
||||
*/
|
||||
public static $statuses = array();
|
||||
|
||||
/**
|
||||
* Result values by event ID.
|
||||
*
|
||||
* @var array<int,array>
|
||||
*/
|
||||
public static $results = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $id Event post ID.
|
||||
*/
|
||||
public function __construct( $id ) {
|
||||
$this->id = absint( $id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event status.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function status() {
|
||||
return self::$statuses[ $this->id ] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event results.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function results() {
|
||||
return self::$results[ $this->id ] ?? array();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open Graph tests.
|
||||
*/
|
||||
class Test_Open_Graph_Tags extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Reset mock SportsPress state.
|
||||
*/
|
||||
public function set_up(): void {
|
||||
parent::set_up();
|
||||
|
||||
if ( property_exists( 'SP_Event', 'statuses' ) ) {
|
||||
SP_Event::$statuses = array();
|
||||
SP_Event::$results = array();
|
||||
}
|
||||
|
||||
update_option( 'sportspress_event_reverse_teams', 'no' );
|
||||
update_option( 'sportspress_event_teams_delimiter', 'vs' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a team.
|
||||
*
|
||||
* @param string $name Team name.
|
||||
* @return int
|
||||
*/
|
||||
private function create_team( $name ) {
|
||||
return self::factory()->post->create(
|
||||
array(
|
||||
'post_type' => 'sp_team',
|
||||
'post_title' => $name,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an event.
|
||||
*
|
||||
* @param array $args Post args.
|
||||
* @return int
|
||||
*/
|
||||
private function create_event( array $args = array() ) {
|
||||
return self::factory()->post->create(
|
||||
wp_parse_args(
|
||||
$args,
|
||||
array(
|
||||
'post_type' => 'sp_event',
|
||||
'post_title' => 'Test Event',
|
||||
'post_status' => 'future',
|
||||
'post_date' => '2026-05-02 13:00:00',
|
||||
'post_content' => 'First pitch at one.',
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Future event emits complete Open Graph data.
|
||||
*/
|
||||
public function test_future_event_emits_core_open_graph_values() {
|
||||
$home = $this->create_team( 'Hawks' );
|
||||
$away = $this->create_team( 'Electrons' );
|
||||
$event = $this->create_event();
|
||||
|
||||
add_post_meta( $event, 'sp_team', $home );
|
||||
add_post_meta( $event, 'sp_team', $away );
|
||||
|
||||
if ( property_exists( 'SP_Event', 'statuses' ) ) {
|
||||
SP_Event::$statuses[ $event ] = 'future';
|
||||
}
|
||||
|
||||
$meta = asc_sp_event_open_graph_data( $event );
|
||||
|
||||
$this->assertSame( 'article', $meta['type'] );
|
||||
$this->assertStringContainsString( 'Hawks vs Electrons', $meta['title'] );
|
||||
$this->assertStringContainsString( 'First pitch at one.', $meta['description'] );
|
||||
$this->assertCount( 2, $meta['images'] );
|
||||
$this->assertSame( '1200', $meta['images'][0]['width'] );
|
||||
$this->assertSame( '628', $meta['images'][0]['height'] );
|
||||
$this->assertSame( '1200', $meta['images'][1]['width'] );
|
||||
$this->assertSame( '1200', $meta['images'][1]['height'] );
|
||||
$this->assertSame( '1200', $meta['image_width'] );
|
||||
$this->assertSame( '628', $meta['image_height'] );
|
||||
$this->assertStringContainsString( '/head-to-head?post=' . $event, $meta['image'] );
|
||||
$this->assertStringContainsString( 'variant=square', $meta['images'][1]['url'] );
|
||||
$this->assertNotEmpty( $meta['url'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Postponed, cancelled, and TBD labels appear in title and description.
|
||||
*
|
||||
* @dataProvider status_provider
|
||||
*
|
||||
* @param string $status Status slug.
|
||||
*/
|
||||
public function test_schedule_status_appears_in_title_and_description( $status ) {
|
||||
$home = $this->create_team( 'Hawks' );
|
||||
$away = $this->create_team( 'Electrons' );
|
||||
$event = $this->create_event();
|
||||
|
||||
add_post_meta( $event, 'sp_team', $home );
|
||||
add_post_meta( $event, 'sp_team', $away );
|
||||
update_post_meta( $event, 'sp_status', $status );
|
||||
|
||||
$meta = asc_sp_event_open_graph_data( $event );
|
||||
$label = strtoupper( $status );
|
||||
|
||||
$this->assertStringStartsWith( $label, $meta['title'] );
|
||||
$this->assertStringStartsWith( $label, $meta['description'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Status provider.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function status_provider() {
|
||||
return array(
|
||||
array( 'postponed' ),
|
||||
array( 'cancelled' ),
|
||||
array( 'tbd' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Result events with scores emit score titles.
|
||||
*/
|
||||
public function test_result_event_with_scores_emits_score_title() {
|
||||
$home = $this->create_team( 'Hawks' );
|
||||
$away = $this->create_team( 'Electrons' );
|
||||
$event = $this->create_event( array( 'post_status' => 'publish' ) );
|
||||
|
||||
add_post_meta( $event, 'sp_team', $home );
|
||||
add_post_meta( $event, 'sp_team', $away );
|
||||
|
||||
if ( property_exists( 'SP_Event', 'statuses' ) ) {
|
||||
SP_Event::$statuses[ $event ] = 'results';
|
||||
SP_Event::$results[ $event ] = array(
|
||||
0 => array( 'r' => 'R' ),
|
||||
$home => array( 'r' => '7' ),
|
||||
$away => array( 'r' => '4' ),
|
||||
);
|
||||
}
|
||||
|
||||
$meta = asc_sp_event_open_graph_data( $event );
|
||||
|
||||
$this->assertStringContainsString( 'Hawks 7-4 Electrons', $meta['title'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Missing teams/results/outcomes still produce valid data.
|
||||
*/
|
||||
public function test_missing_sportspress_data_does_not_break_meta_generation() {
|
||||
$event = $this->create_event(
|
||||
array(
|
||||
'post_title' => 'Sparse Event',
|
||||
'post_content' => '',
|
||||
)
|
||||
);
|
||||
|
||||
if ( property_exists( 'SP_Event', 'statuses' ) ) {
|
||||
SP_Event::$statuses[ $event ] = 'results';
|
||||
SP_Event::$results[ $event ] = array();
|
||||
}
|
||||
|
||||
$meta = asc_sp_event_open_graph_data( $event );
|
||||
|
||||
$this->assertSame( 'Sparse Event', $meta['title'] );
|
||||
$this->assertNotEmpty( $meta['description'] );
|
||||
$this->assertSame( '1200', $meta['image_width'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML-heavy post content is stripped and escaped in rendered tags.
|
||||
*/
|
||||
public function test_description_strips_html_and_rendered_tags_are_escaped() {
|
||||
$home = $this->create_team( 'Hawks "A"' );
|
||||
$away = $this->create_team( 'Electrons <B>' );
|
||||
$event = $this->create_event(
|
||||
array(
|
||||
'post_content' => '<script>alert("x")</script><p>Bring <strong>bats</strong> & gloves.</p>',
|
||||
)
|
||||
);
|
||||
|
||||
add_post_meta( $event, 'sp_team', $home );
|
||||
add_post_meta( $event, 'sp_team', $away );
|
||||
|
||||
$meta = asc_sp_event_open_graph_data( $event );
|
||||
|
||||
$this->assertStringNotContainsString( '<script', $meta['description'] );
|
||||
$this->assertStringContainsString( 'Bring bats & gloves.', $meta['description'] );
|
||||
|
||||
$GLOBALS['post'] = get_post( $event );
|
||||
$GLOBALS['wp_query']->is_single = true;
|
||||
|
||||
ob_start();
|
||||
custom_open_graph_tags_with_sportspress_integration();
|
||||
$output = ob_get_clean();
|
||||
|
||||
$this->assertStringContainsString( 'og:image:width', $output );
|
||||
$this->assertSame( 2, substr_count( $output, 'property="og:image" content=' ) );
|
||||
$this->assertStringContainsString( 'content="628"', $output );
|
||||
$this->assertStringContainsString( 'content="1200"', $output );
|
||||
$this->assertStringContainsString( 'variant=square', $output );
|
||||
$this->assertStringContainsString( 'Hawks "A"', $output );
|
||||
$this->assertStringNotContainsString( '<B>', $output );
|
||||
$this->assertStringNotContainsString( '<script', $output );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user