Files
heretek-control-core/tests/MetaBoxAbilitiesTest.php
T

244 lines
13 KiB
PHP

<?php
use PHPUnit\Framework\TestCase;
final class MetaBoxAbilitiesTest extends TestCase {
private EMCP_Tools_Meta_Box_Abilities $mb;
protected function setUp(): void {
emcp_test_reset();
$GLOBALS['emcp_test']['caps'] = array( 'edit_posts', 'manage_options' );
$this->mb = new EMCP_Tools_Meta_Box_Abilities();
}
public function test_metabox_active_true_when_rwmb_ver_defined(): void {
$this->assertTrue( EMCP_Tools_Meta_Box_Abilities::metabox_active() );
}
public function test_register_adds_two_dispatchers(): void {
$this->mb->register();
$names = $this->mb->get_ability_names();
$this->assertContains( 'emcp-tools/metabox-read', $names );
$this->assertContains( 'emcp-tools/metabox-write', $names );
}
public function test_read_dispatcher_no_operation_returns_catalog(): void {
$out = $this->mb->run_metabox_read( array() );
$this->assertSame( 'read', $out['mode'] );
$ops = array_column( $out['operations'], 'operation' );
$this->assertEqualsCanonicalizing(
array( 'list-field-groups', 'get-field-group', 'get-fields' ), $ops
);
}
public function test_write_dispatcher_no_operation_returns_catalog(): void {
$out = $this->mb->run_metabox_write( array() );
$this->assertSame( 'write', $out['mode'] );
$this->assertSame( array( 'update-fields' ), array_column( $out['operations'], 'operation' ) );
}
public function test_unknown_operation_returns_wp_error(): void {
$out = $this->mb->run_metabox_read( array( 'operation' => 'nope' ) );
$this->assertInstanceOf( WP_Error::class, $out );
$this->assertSame( 'unknown_operation', $out->get_error_code() );
}
public function test_wrong_mode_operation_rejected(): void {
// update-fields is a write op; calling it via the read dispatcher must fail.
$out = $this->mb->run_metabox_read( array( 'operation' => 'update-fields' ) );
$this->assertInstanceOf( WP_Error::class, $out );
$this->assertSame( 'unknown_operation', $out->get_error_code() );
}
private function seed_boxes(): void {
$GLOBALS['emcp_test']['metabox']['boxes'] = array(
new EMCP_Test_MB( array(
'id' => 'event_details',
'title' => 'Event Details',
'post_types' => array( 'event' ),
'fields' => array(
array( 'id' => 'event_date', 'name' => 'Event Date', 'type' => 'date' ),
array( 'id' => 'venue', 'name' => 'Venue', 'type' => 'text', 'std' => 'TBD' ),
array( 'id' => 'lineup', 'name' => 'Lineup', 'type' => 'group', 'fields' => array(
array( 'id' => 'act', 'name' => 'Act', 'type' => 'text' ),
) ),
),
), 'post' ),
new EMCP_Test_MB( array(
'id' => 'seo_meta', 'title' => 'SEO', 'post_types' => array( 'post', 'page' ),
'fields' => array( array( 'id' => 'seo_title', 'name' => 'SEO Title', 'type' => 'text' ) ),
), 'post' ),
);
}
public function test_list_field_groups_returns_rows(): void {
$this->seed_boxes();
$out = $this->mb->run_metabox_read( array( 'operation' => 'list-field-groups' ) );
$this->assertSame( 2, $out['total'] );
$ids = array_column( $out['field_groups'], 'id' );
$this->assertEqualsCanonicalizing( array( 'event_details', 'seo_meta' ), $ids );
$event = array_values( array_filter( $out['field_groups'], fn( $g ) => 'event_details' === $g['id'] ) )[0];
$this->assertSame( 'Event Details', $event['title'] );
$this->assertSame( 'post', $event['object_type'] );
$this->assertSame( array( 'event' ), $event['post_types'] );
$this->assertSame( 3, $event['field_count'] );
}
public function test_list_field_groups_search_filter(): void {
$this->seed_boxes();
$out = $this->mb->run_metabox_read( array( 'operation' => 'list-field-groups', 'arguments' => array( 'search' => 'seo' ) ) );
$this->assertSame( 1, $out['total'] );
$this->assertSame( 'seo_meta', $out['field_groups'][0]['id'] );
}
public function test_get_field_group_returns_fields_with_nesting(): void {
$this->seed_boxes();
$out = $this->mb->run_metabox_read( array( 'operation' => 'get-field-group', 'arguments' => array( 'id' => 'event_details' ) ) );
$this->assertSame( 'event_details', $out['id'] );
$this->assertCount( 3, $out['fields'] );
$venue = $out['fields'][1];
$this->assertSame( 'venue', $venue['id'] );
$this->assertSame( 'Venue', $venue['label'] ); // MB `name` -> our `label`
$this->assertSame( 'text', $venue['type'] );
$this->assertSame( 'TBD', $venue['std'] );
$group = $out['fields'][2];
$this->assertArrayHasKey( 'fields', $group );
$this->assertSame( 'act', $group['fields'][0]['id'] );
}
public function test_get_field_group_unknown_id_errors(): void {
$this->seed_boxes();
$out = $this->mb->run_metabox_read( array( 'operation' => 'get-field-group', 'arguments' => array( 'id' => 'nope' ) ) );
$this->assertInstanceOf( WP_Error::class, $out );
$this->assertSame( 'group_not_found', $out->get_error_code() );
}
public function test_get_fields_reads_values_for_post(): void {
$this->seed_boxes();
$GLOBALS['emcp_test']['posts'][10] = (object) array( 'ID' => 10, 'post_type' => 'event' );
$GLOBALS['emcp_test']['metabox']['values']['post']['10'] = array(
'event_date' => '2026-08-01', 'venue' => 'The Roxy',
);
$out = $this->mb->run_metabox_read( array( 'operation' => 'get-fields', 'arguments' => array( 'post_id' => 10 ) ) );
$this->assertSame( array( 'object_type' => 'post', 'object_id' => 10 ), $out['target'] );
$this->assertSame( '2026-08-01', $out['fields']['event_date'] );
$this->assertSame( 'The Roxy', $out['fields']['venue'] );
// seo_meta applies to post/page, not to 'event' -> its field is absent.
$this->assertArrayNotHasKey( 'seo_title', $out['fields'] );
}
public function test_get_fields_filter_subset(): void {
$this->seed_boxes();
$GLOBALS['emcp_test']['posts'][10] = (object) array( 'ID' => 10, 'post_type' => 'event' );
$GLOBALS['emcp_test']['metabox']['values']['post']['10'] = array( 'event_date' => 'x', 'venue' => 'y' );
$out = $this->mb->run_metabox_read( array( 'operation' => 'get-fields', 'arguments' => array( 'post_id' => 10, 'fields' => array( 'venue' ) ) ) );
$this->assertSame( array( 'venue' => 'y' ), $out['fields'] );
}
public function test_get_fields_missing_target_errors(): void {
$out = $this->mb->run_metabox_read( array( 'operation' => 'get-fields', 'arguments' => array() ) );
$this->assertInstanceOf( WP_Error::class, $out );
$this->assertSame( 'invalid_target', $out->get_error_code() );
}
public function test_get_fields_unknown_post_errors(): void {
$out = $this->mb->run_metabox_read( array( 'operation' => 'get-fields', 'arguments' => array( 'post_id' => 999 ) ) );
$this->assertInstanceOf( WP_Error::class, $out );
$this->assertSame( 'post_not_found', $out->get_error_code() );
}
public function test_get_fields_normalizes_image_value(): void {
$this->seed_boxes();
$GLOBALS['emcp_test']['posts'][10] = (object) array( 'ID' => 10, 'post_type' => 'event' );
$GLOBALS['emcp_test']['metabox']['values']['post']['10'] = array(
'venue' => array( 'ID' => 55, 'url' => 'http://x/p.jpg', 'full_url' => 'http://x/p.jpg', 'alt' => 'hall', 'title' => 'Hall', 'width' => 800 ),
);
$out = $this->mb->run_metabox_read( array( 'operation' => 'get-fields', 'arguments' => array( 'post_id' => 10, 'fields' => array( 'venue' ) ) ) );
$this->assertSame( array( 'id' => 55, 'url' => 'http://x/p.jpg', 'alt' => 'hall', 'title' => 'Hall' ), $out['fields']['venue'] );
}
public function test_update_fields_writes_and_rereads(): void {
$this->seed_boxes();
$GLOBALS['emcp_test']['posts'][10] = (object) array( 'ID' => 10, 'post_type' => 'event' );
$out = $this->mb->run_metabox_write( array(
'operation' => 'update-fields',
'arguments' => array( 'post_id' => 10, 'fields' => array( 'venue' => 'Wembley' ) ),
) );
$this->assertSame( array( 'venue' ), $out['updated'] );
$this->assertSame( array(), $out['skipped'] );
$this->assertSame( 'Wembley', $out['values']['venue'] );
// Confirm it actually persisted to the fixture store.
$this->assertSame( 'Wembley', $GLOBALS['emcp_test']['metabox']['values']['post']['10']['venue'] );
}
public function test_update_fields_skips_unknown_field(): void {
$this->seed_boxes();
$GLOBALS['emcp_test']['posts'][10] = (object) array( 'ID' => 10, 'post_type' => 'event' );
$out = $this->mb->run_metabox_write( array(
'operation' => 'update-fields',
'arguments' => array( 'post_id' => 10, 'fields' => array( 'not_a_field' => 'x' ) ),
) );
$this->assertSame( array(), $out['updated'] );
$this->assertSame( array( array( 'field' => 'not_a_field', 'reason' => 'field_not_found' ) ), $out['skipped'] );
}
public function test_update_fields_requires_fields_map(): void {
$this->seed_boxes();
$GLOBALS['emcp_test']['posts'][10] = (object) array( 'ID' => 10, 'post_type' => 'event' );
$out = $this->mb->run_metabox_write( array( 'operation' => 'update-fields', 'arguments' => array( 'post_id' => 10 ) ) );
$this->assertInstanceOf( WP_Error::class, $out );
$this->assertSame( 'missing_params', $out->get_error_code() );
}
public function test_get_fields_forbidden_via_post_id_when_no_edit_post_cap(): void {
$this->seed_boxes();
$GLOBALS['emcp_test']['posts'][10] = (object) array( 'ID' => 10, 'post_type' => 'event' );
$GLOBALS['emcp_test']['post_caps'][10] = false; // edit_posts yes, edit_post(10) no.
$out = $this->mb->run_metabox_read( array( 'operation' => 'get-fields', 'arguments' => array( 'post_id' => 10 ) ) );
$this->assertInstanceOf( WP_Error::class, $out );
$this->assertSame( 'forbidden', $out->get_error_code() );
}
public function test_get_fields_forbidden_via_object_type_post_when_no_edit_post_cap(): void {
// Regression for I-1: object_type=post + object_id must be gated identically to post_id.
$this->seed_boxes();
$GLOBALS['emcp_test']['posts'][10] = (object) array( 'ID' => 10, 'post_type' => 'event' );
$GLOBALS['emcp_test']['post_caps'][10] = false; // edit_posts yes, edit_post(10) no.
$out = $this->mb->run_metabox_read( array(
'operation' => 'get-fields',
'arguments' => array( 'object_type' => 'post', 'object_id' => 10 ),
) );
$this->assertInstanceOf( WP_Error::class, $out );
$this->assertSame( 'forbidden', $out->get_error_code() );
}
public function test_update_fields_forbidden_via_object_type_post_when_no_edit_post_cap(): void {
// Same hole on the write path.
$this->seed_boxes();
$GLOBALS['emcp_test']['posts'][10] = (object) array( 'ID' => 10, 'post_type' => 'event' );
$GLOBALS['emcp_test']['post_caps'][10] = false; // edit_posts yes, edit_post(10) no.
$out = $this->mb->run_metabox_write( array(
'operation' => 'update-fields',
'arguments' => array( 'object_type' => 'post', 'object_id' => 10, 'fields' => array( 'venue' => 'Wembley' ) ),
) );
$this->assertInstanceOf( WP_Error::class, $out );
$this->assertSame( 'forbidden', $out->get_error_code() );
}
public function test_get_fields_object_type_post_unknown_id_returns_post_not_found(): void {
// Regression for the carried 404 gap: object_type=post + object_id must 404 just like post_id.
$out = $this->mb->run_metabox_read( array(
'operation' => 'get-fields',
'arguments' => array( 'object_type' => 'post', 'object_id' => 999 ),
) );
$this->assertInstanceOf( WP_Error::class, $out );
$this->assertSame( 'post_not_found', $out->get_error_code() );
}
public function test_no_delete_operation_exists(): void {
foreach ( array( 'delete-fields', 'delete-field-group', 'remove-fields' ) as $op ) {
$out = $this->mb->run_metabox_write( array( 'operation' => $op ) );
$this->assertInstanceOf( WP_Error::class, $out, "op $op must not exist" );
}
}
}