*/ private array $saved = []; protected function setUpSaveStubs(): void { $this->saved = []; \Brain\Monkey\Functions\stubs( [ 'update_post_meta' => function ( $post_id, $key, $value ) { $this->saved[ $key ] = $value; return true; }, 'wp_verify_nonce' => function () { return true; }, 'wp_nonce_field' => function () { /* noop */ }, ] ); } public function test_save_persists_provider_and_repo_url(): void { $this->setUpSaveStubs(); $_POST['projects_portfolio_meta_box_nonce'] = 'nonce'; $_POST['projects_portfolio_provider'] = 'gitea'; $_POST['projects_portfolio_repo_url'] = 'https://codeberg.org/owner/repo'; $_POST['projects_portfolio_gitea_base_url'] = 'https://codeberg.org'; projects_portfolio_save_meta_box( 42 ); $this->assertSame( 'gitea', $this->saved['_projects_portfolio_provider'] ); $this->assertSame( 'https://codeberg.org/owner/repo', $this->saved['_projects_portfolio_repo_url'] ); $this->assertSame( 'https://codeberg.org', $this->saved['_projects_portfolio_gitea_base_url'] ); } public function test_lazy_migration_from_legacy_url(): void { $existing = [ '_projects_portfolio_provider' => '', '_projects_portfolio_repo_url' => '', '_projects_portfolio_github_url' => 'https://github.com/owner/repo', ]; \Brain\Monkey\Functions\stubs( [ 'get_post_meta' => function ( $post_id, $key, $single = false ) use ( &$existing ) { return $existing[ $key ] ?? ''; }, 'update_post_meta' => function ( $post_id, $key, $value ) use ( &$existing ) { $existing[ $key ] = $value; return true; }, 'wp_verify_nonce' => function () { return true; }, 'wp_nonce_field' => function () { /* noop */ }, ] ); // No $_POST provider/repo_url. Lazy migration should fire. $_POST['projects_portfolio_meta_box_nonce'] = 'nonce'; projects_portfolio_save_meta_box( 1 ); $this->assertSame( 'github', $existing['_projects_portfolio_provider'] ); $this->assertSame( 'https://github.com/owner/repo', $existing['_projects_portfolio_repo_url'] ); } public function test_switching_to_gitea_deletes_legacy_github_url(): void { $existing = [ '_projects_portfolio_provider' => 'github', '_projects_portfolio_repo_url' => 'https://github.com/owner/repo', '_projects_portfolio_github_url' => 'https://github.com/owner/repo', ]; $deleted = []; \Brain\Monkey\Functions\stubs( [ 'get_post_meta' => function ( $post_id, $key, $single = false ) use ( &$existing ) { return $existing[ $key ] ?? ''; }, 'update_post_meta' => function ( $post_id, $key, $value ) use ( &$existing ) { $existing[ $key ] = $value; return true; }, 'delete_post_meta' => function ( $post_id, $key ) use ( &$existing, &$deleted ) { unset( $existing[ $key ] ); $deleted[] = $key; return true; }, 'wp_verify_nonce' => function () { return true; }, 'wp_nonce_field' => function () { /* noop */ }, ] ); $_POST['projects_portfolio_meta_box_nonce'] = 'nonce'; $_POST['projects_portfolio_provider'] = 'gitea'; $_POST['projects_portfolio_repo_url'] = 'https://codeberg.org/owner/repo'; projects_portfolio_save_meta_box( 7 ); $this->assertSame( 'gitea', $existing['_projects_portfolio_provider'] ); $this->assertSame( 'https://codeberg.org/owner/repo', $existing['_projects_portfolio_repo_url'] ); $this->assertArrayNotHasKey( '_projects_portfolio_github_url', $existing ); $this->assertContains( '_projects_portfolio_github_url', $deleted ); } public function test_github_with_empty_repo_url_deletes_legacy_github_url(): void { $existing = [ '_projects_portfolio_provider' => 'github', '_projects_portfolio_repo_url' => 'https://github.com/owner/repo', '_projects_portfolio_github_url' => 'https://github.com/owner/repo', ]; $deleted = []; \Brain\Monkey\Functions\stubs( [ 'get_post_meta' => function ( $post_id, $key, $single = false ) use ( &$existing ) { return $existing[ $key ] ?? ''; }, 'update_post_meta' => function ( $post_id, $key, $value ) use ( &$existing ) { $existing[ $key ] = $value; return true; }, 'delete_post_meta' => function ( $post_id, $key ) use ( &$existing, &$deleted ) { unset( $existing[ $key ] ); $deleted[] = $key; return true; }, 'wp_verify_nonce' => function () { return true; }, 'wp_nonce_field' => function () { /* noop */ }, ] ); $_POST['projects_portfolio_meta_box_nonce'] = 'nonce'; $_POST['projects_portfolio_provider'] = 'github'; $_POST['projects_portfolio_repo_url'] = ''; projects_portfolio_save_meta_box( 9 ); $this->assertSame( 'github', $existing['_projects_portfolio_provider'] ); $this->assertSame( '', $existing['_projects_portfolio_repo_url'] ); $this->assertArrayNotHasKey( '_projects_portfolio_github_url', $existing ); $this->assertContains( '_projects_portfolio_github_url', $deleted ); } public function test_website_mode_persists_website_fields_and_clears_repo_fields(): void { $existing = [ '_projects_portfolio_provider' => 'github', '_projects_portfolio_repo_url' => 'https://github.com/owner/repo', '_projects_portfolio_github_url' => 'https://github.com/owner/repo', ]; $deleted = []; \Brain\Monkey\Functions\stubs( [ 'get_post_meta' => function ( $post_id, $key, $single = false ) use ( &$existing ) { return $existing[ $key ] ?? ''; }, 'update_post_meta' => function ( $post_id, $key, $value ) use ( &$existing ) { $existing[ $key ] = $value; return true; }, 'delete_post_meta' => function ( $post_id, $key ) use ( &$existing, &$deleted ) { unset( $existing[ $key ] ); $deleted[] = $key; return true; }, 'wp_verify_nonce' => function () { return true; }, 'wp_nonce_field' => function () { /* noop */ }, ] ); $_POST['projects_portfolio_meta_box_nonce'] = 'nonce'; $_POST['projects_portfolio_is_website'] = '1'; $_POST['projects_portfolio_site_url'] = 'https://example.com'; $_POST['projects_portfolio_site_platform'] = 'WordPress'; $_POST['projects_portfolio_site_screenshot_id'] = '42'; projects_portfolio_save_meta_box( 11 ); $this->assertSame( '1', $existing['_projects_portfolio_is_website'] ); $this->assertSame( 'https://example.com', $existing['_projects_portfolio_site_url'] ); $this->assertSame( 'WordPress', $existing['_projects_portfolio_site_platform'] ); $this->assertSame( 42, $existing['_projects_portfolio_site_screenshot_id'] ); // Repo-mode fields should be cleared when switching to website mode. $this->assertArrayNotHasKey( '_projects_portfolio_provider', $existing ); $this->assertArrayNotHasKey( '_projects_portfolio_repo_url', $existing ); $this->assertArrayNotHasKey( '_projects_portfolio_github_url', $existing ); $this->assertContains( '_projects_portfolio_provider', $deleted ); $this->assertContains( '_projects_portfolio_repo_url', $deleted ); $this->assertContains( '_projects_portfolio_github_url', $deleted ); } public function test_unchecked_website_checkbox_clears_website_fields(): void { $existing = [ '_projects_portfolio_is_website' => '1', '_projects_portfolio_site_url' => 'https://example.com', '_projects_portfolio_site_platform' => 'WordPress', '_projects_portfolio_site_screenshot_id' => 42, ]; $deleted = []; \Brain\Monkey\Functions\stubs( [ 'get_post_meta' => function ( $post_id, $key, $single = false ) use ( &$existing ) { return $existing[ $key ] ?? ''; }, 'update_post_meta' => function ( $post_id, $key, $value ) use ( &$existing ) { $existing[ $key ] = $value; return true; }, 'delete_post_meta' => function ( $post_id, $key ) use ( &$existing, &$deleted ) { unset( $existing[ $key ] ); $deleted[] = $key; return true; }, 'wp_verify_nonce' => function () { return true; }, 'wp_nonce_field' => function () { /* noop */ }, ] ); $_POST['projects_portfolio_meta_box_nonce'] = 'nonce'; // is_website is absent — repo mode is implied. projects_portfolio_save_meta_box( 13 ); $this->assertArrayNotHasKey( '_projects_portfolio_is_website', $existing ); $this->assertArrayNotHasKey( '_projects_portfolio_site_url', $existing ); $this->assertArrayNotHasKey( '_projects_portfolio_site_platform', $existing ); $this->assertArrayNotHasKey( '_projects_portfolio_site_screenshot_id', $existing ); $this->assertContains( '_projects_portfolio_is_website', $deleted ); } public function test_existing_repo_post_without_checkbox_keeps_repo_fields(): void { $existing = [ '_projects_portfolio_provider' => 'github', '_projects_portfolio_repo_url' => 'https://github.com/owner/repo', ]; \Brain\Monkey\Functions\stubs( [ 'get_post_meta' => function ( $post_id, $key, $single = false ) use ( &$existing ) { return $existing[ $key ] ?? ''; }, 'update_post_meta' => function ( $post_id, $key, $value ) use ( &$existing ) { $existing[ $key ] = $value; return true; }, 'delete_post_meta' => function () { return true; }, 'wp_verify_nonce' => function () { return true; }, 'wp_nonce_field' => function () { /* noop */ }, ] ); $_POST['projects_portfolio_meta_box_nonce'] = 'nonce'; // is_website not present in $_POST — should be treated as unchecked. projects_portfolio_save_meta_box( 17 ); $this->assertSame( 'github', $existing['_projects_portfolio_provider'] ); $this->assertSame( 'https://github.com/owner/repo', $existing['_projects_portfolio_repo_url'] ); $this->assertArrayNotHasKey( '_projects_portfolio_is_website', $existing ); } }