diff --git a/src/Integration/WordPressSeo/Breadcrumbs.php b/src/Integration/WordPressSeo/Breadcrumbs.php index 5124fc4..86eb05e 100644 --- a/src/Integration/WordPressSeo/Breadcrumbs.php +++ b/src/Integration/WordPressSeo/Breadcrumbs.php @@ -8,7 +8,6 @@ use Yoast\WP\SEO\Context\Meta_Tags_Context; use Yoast\WP\SEO\Main; use Yoast\WP\SEO\Models\Indexable; -use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Surfaces\Values\Meta; /** @@ -26,7 +25,6 @@ public function __construct( public function registerHooks(): void { - add_filter('wpseo_breadcrumb_indexables', [$this, 'fixHomeBreadcrumbs'], 10, 2); add_filter('wpseo_breadcrumb_indexables', [$this, 'fixTaxonomyBreadcrumbs'], 10, 2); add_filter('wpseo_breadcrumb_indexables', [$this, 'fixPostBreadcrumbs'], 10, 2); } @@ -125,52 +123,6 @@ public function fixTaxonomyBreadcrumbs(array $indexables, Meta_Tags_Context $con return $indexables; } - /** - * Fix Yoast breadcrumbs on home. - * - * @param Indexable[] $indexables - * @return Indexable[] - */ - public function fixHomeBreadcrumbs(array $indexables, Meta_Tags_Context $context): array - { - if (!$this->api->isQueryPageForCustomPostType()) { - return $indexables; - } - - $yoast = $this->getYoast(); - - if ($yoast->helpers->current_page->get_page_type() !== 'Home_Page') { - return $indexables; - } - - /** @var Indexable_Repository $indexableRepository */ - $indexableRepository = $yoast->classes->get(Indexable_Repository::class); - $staticAncestors = []; - - $breadcrumbsHome = $yoast->helpers->options->get('breadcrumbs-home'); - - if ($breadcrumbsHome !== '') { - $frontPageId = $yoast->helpers->current_page->get_front_page_id(); - - $staticAncestor = $frontPageId === 0 - ? $indexableRepository->find_for_home_page() - : $indexableRepository->find_by_id_and_type($frontPageId, 'post'); - - if ( - $staticAncestor instanceof Indexable - && ($frontPageId === 0 || $staticAncestor->post_status !== 'unindexed') - ) { - $staticAncestors[] = $staticAncestor; - } - } - - if (!empty($staticAncestors)) { - array_unshift($indexables, ...$staticAncestors); - } - - return $indexables; - } - private function getYoast(): Main { /** @var Main */ diff --git a/src/functions.php b/src/functions.php index 3152af2..d4d995b 100644 --- a/src/functions.php +++ b/src/functions.php @@ -73,6 +73,10 @@ function get_page_url_for_custom_post_type(?string $postType = null): ?string } namespace { + // Deprecated back-compat shims. The function_exists() guards run during + // Composer's files autoload, before coverage starts, so they cannot be + // covered; the function bodies are exercised by PublicApiTest. + // @codeCoverageIgnoreStart if (!\function_exists('is_page_for_custom_post_type')) { /** * @deprecated 1.0.0 Use \n5s\PageForCustomPostType\is_page_for_custom_post_type() instead. @@ -124,4 +128,5 @@ function get_page_url_for_custom_post_type(?string $postType = null): ?string return \n5s\PageForCustomPostType\get_page_url_for_custom_post_type($postType); } } + // @codeCoverageIgnoreEnd } diff --git a/tests/Integration/AdminScreenTest.php b/tests/Integration/AdminScreenTest.php index 1816368..c76cc6e 100644 --- a/tests/Integration/AdminScreenTest.php +++ b/tests/Integration/AdminScreenTest.php @@ -24,7 +24,7 @@ protected function setUp(): void if (!\function_exists('add_settings_section')) { require_once \ABSPATH . 'wp-admin/includes/template.php'; } - if (!\function_exists('register_setting')) { + if (!\function_exists('add_submenu_page')) { require_once \ABSPATH . 'wp-admin/includes/plugin.php'; } diff --git a/tests/Integration/Integration/AutodescriptionTest.php b/tests/Integration/Integration/AutodescriptionTest.php index 58e1340..af9d744 100644 --- a/tests/Integration/Integration/AutodescriptionTest.php +++ b/tests/Integration/Integration/AutodescriptionTest.php @@ -4,6 +4,10 @@ namespace n5s\PageForCustomPostType\Tests\Integration\Integration; +use n5s\PageForCustomPostType\Core\Api; +use n5s\PageForCustomPostType\Integration\Autodescription\Autodescription; +use n5s\PageForCustomPostType\Integration\Autodescription\Breadcrumbs; +use n5s\PageForCustomPostType\Integration\Autodescription\QueryType; use n5s\PageForCustomPostType\Tests\Fixtures\TestCase; use PHPUnit\Framework\Attributes\RequiresFunction; use PHPUnit\Framework\Attributes\WithoutErrorHandler; @@ -195,6 +199,60 @@ public function testPostTypeWithoutPfcptPageHasNoBreadcrumbChange(): void } } + public function testIsSupported(): void + { + $autodescription = new Autodescription(new QueryType(new Api()), new Breadcrumbs(new Api())); + + $this->assertTrue($autodescription->isSupported()); + } + + public function testRegisterHooksRegistersSubIntegrations(): void + { + $queryType = new QueryType(new Api()); + $breadcrumbs = new Breadcrumbs(new Api()); + + (new Autodescription($queryType, $breadcrumbs))->registerHooks(); + + $this->assertNotFalse(has_filter('the_seo_framework_is_singular_archive', [$queryType, 'markPfcptAsSingularArchive'])); + $this->assertNotFalse(has_filter('the_seo_framework_breadcrumb_list', [$breadcrumbs, 'addPfcptPageCrumb'])); + } + + public function testSingularArchiveShortCircuitsWhenAlreadyTrue(): void + { + $queryType = new QueryType(new Api()); + + $this->assertTrue($queryType->markPfcptAsSingularArchive(true, null)); + } + + public function testSingularArchiveResolvedFromExplicitPageId(): void + { + $queryType = new QueryType(new Api()); + + $this->assertTrue($queryType->markPfcptAsSingularArchive(false, $this->homeForBookId)); + + $regularId = self::factory()->post->create(['post_type' => 'post']); + $this->assertFalse($queryType->markPfcptAsSingularArchive(false, $regularId)); + } + + public function testBreadcrumbCrumbSkippedForUnresolvableArgs(): void + { + $breadcrumbs = new Breadcrumbs(new Api()); + $list = [['url' => home_url('/'), 'name' => 'Home']]; + + // id is neither int nor WP_Post + $this->assertSame($list, $breadcrumbs->addPfcptPageCrumb($list, ['id' => 'not-an-id'])); + // id points to a non-existent post + $this->assertSame($list, $breadcrumbs->addPfcptPageCrumb($list, ['id' => 999999])); + // taxonomy is not a string + $this->assertSame($list, $breadcrumbs->addPfcptPageCrumb($list, ['id' => 1, 'tax' => 123])); + // neither a single post nor a taxonomy archive + $this->assertSame($list, $breadcrumbs->addPfcptPageCrumb($list, ['uid' => 7])); + // unknown taxonomy + $this->assertSame($list, $breadcrumbs->addPfcptPageCrumb($list, ['id' => 1, 'tax' => 'does_not_exist'])); + // real taxonomy with no PFCPT-bound post type + $this->assertSame($list, $breadcrumbs->addPfcptPageCrumb($list, ['id' => 1, 'tax' => 'category'])); + } + /** * Clear TSF's internal memoization cache between tests. * diff --git a/tests/Integration/Integration/WordPressSeoTest.php b/tests/Integration/Integration/WordPressSeoTest.php index a1ab42a..8bdbf55 100644 --- a/tests/Integration/Integration/WordPressSeoTest.php +++ b/tests/Integration/Integration/WordPressSeoTest.php @@ -4,6 +4,11 @@ namespace n5s\PageForCustomPostType\Tests\Integration\Integration; +use n5s\PageForCustomPostType\Core\Api; +use n5s\PageForCustomPostType\Integration\WordPressSeo\Breadcrumbs; +use n5s\PageForCustomPostType\Integration\WordPressSeo\Indexables; +use n5s\PageForCustomPostType\Integration\WordPressSeo\Schema; +use n5s\PageForCustomPostType\Integration\WordPressSeo\WordPressSeo; use n5s\PageForCustomPostType\Tests\Fixtures\TestCase; use PHPUnit\Framework\Attributes\RequiresFunction; use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer; @@ -169,4 +174,78 @@ public function testSchemaWebpageTypeUnchangedOnRegularPage(): void $this->assertEquals('WebPage', $type); } + + public function testSchemaWebpageTypeDoesNotDuplicateCollectionPage(): void + { + $this->get($this->getBookHomeUrl()); + + // CollectionPage already present: the filter must return it unchanged. + $type = apply_filters('wpseo_schema_webpage_type', ['WebPage', 'CollectionPage']); + + $this->assertSame(['WebPage', 'CollectionPage'], $type); + } + + public function testIsSupported(): void + { + $seo = new WordPressSeo(new Schema(new Api()), new Breadcrumbs(new Api()), new Indexables(new Api())); + + $this->assertTrue($seo->isSupported()); + } + + public function testRegisterHooksRegistersAllSubIntegrations(): void + { + $schema = new Schema(new Api()); + $breadcrumbs = new Breadcrumbs(new Api()); + $indexables = new Indexables(new Api()); + + (new WordPressSeo($schema, $breadcrumbs, $indexables))->registerHooks(); + + $this->assertNotFalse(has_filter('wpseo_schema_webpage_type', [$schema, 'addCollectionPageType'])); + $this->assertNotFalse(has_filter('wpseo_breadcrumb_indexables', [$breadcrumbs, 'fixPostBreadcrumbs'])); + $this->assertNotFalse(has_filter('wpseo_breadcrumb_indexables', [$breadcrumbs, 'fixTaxonomyBreadcrumbs'])); + $this->assertNotFalse(has_action('wp', [$indexables, 'configurePageDetection'])); + } + + public function testPageDetectionResolvesPfcptPageWithStaticFrontPage(): void + { + // With a static front page, Yoast's Current_Page_Helper would resolve the + // posts page instead of the PFCPT page. Indexables::configurePageDetection + // intercepts show_on_front (only for that helper) so detection falls through + // to the PFCPT page id. + $this->configureStaticFrontPage(); + + $memoizer = \YoastSEO()->classes->get(Meta_Tags_Context_Memoizer::class); + $memoizer->clear(); + + $this->get($this->getBookHomeUrl()); + + $meta = \YoastSEO()->meta->for_current_page(); + + $this->assertSame($this->getBookHomeUrl(), $meta->canonical); + } + + public function testTaxonomyBreadcrumbsSkippedForNonMainTaxonomy(): void + { + // The breadcrumb fix only applies when the current taxonomy is the post + // type's configured main taxonomy. Point books' main taxonomy elsewhere + // and confirm the genre archive is left untouched (no PFCPT crumb). + \YoastSEO()->helpers->options->set('post_types-' . self::BOOK_POST_TYPE . '-maintax', 'category'); + + $genreId = $this->getOrCreateTerm(self::GENRE_TAXONOMY, 'Fantasy'); + foreach ($this->bookIds as $bookId) { + wp_set_object_terms($bookId, $genreId, self::GENRE_TAXONOMY); + } + + $memoizer = \YoastSEO()->classes->get(Meta_Tags_Context_Memoizer::class); + $memoizer->clear(); + + $genre = get_term($genreId, self::GENRE_TAXONOMY); + $this->get(get_term_link($genre)); + + $meta = \YoastSEO()->meta->for_current_page(); + + foreach ($meta->breadcrumbs as $crumb) { + $this->assertNotSame($this->homeForBookId, \is_array($crumb) ? ($crumb['id'] ?? null) : null); + } + } } diff --git a/tests/Integration/PublicApiTest.php b/tests/Integration/PublicApiTest.php index 47ba0d9..3efdff6 100644 --- a/tests/Integration/PublicApiTest.php +++ b/tests/Integration/PublicApiTest.php @@ -97,6 +97,14 @@ public function testGetPageIdForCustomPostTypeUsesCurrentQueryWhenNull(): void ); } + public function testGetPageIdForCustomPostTypeReturnsNullOnRegularPage(): void + { + $this->setExpectedDeprecated('get_page_id_for_custom_post_type'); + $this->get(get_permalink($this->staticFrontPageId)); + + $this->assertNull(get_page_id_for_custom_post_type()); + } + public function testGetPageUrlForCustomPostTypeReturnsUrl(): void { $this->setExpectedDeprecated('get_page_url_for_custom_post_type'); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 445b5dd..e08607b 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -58,6 +58,16 @@ return; } + // Polylang only loads its API (the pll_* functions and PLL()) when + // init() enters a context via init_context(). In PHPUnit there is no + // request context and no languages exist yet, so init() detects an + // empty context and returns before requiring src/api.php — leaving + // PLL() undefined and every PolylangTest skipped. Force a frontend + // context so the API loads; languages are (re)created per test by + // TestCase::setPolylangDefaultLanguage() after Refresh_Database rolls + // them back. + add_filter('pll_context', static fn (string $class): string => $class ?: 'PLL_Frontend'); + $polylangBootstrap = new \Polylang(); $polylangBootstrap->init(); });