P1-A: CONTEXT.md — add glossary entries for _prab_* namespace, meta-reader pattern, ScholarlyArticle type, and honest-reviewedBy integrity constraint (Person-vs-Organization branching rule). P1-B: Master IA + app-map updated (workspace docs, not in this diff): INFORMATION-ARCHITECTURE.md v1.3.2 — §5 cross-plugin data flow added (PRAutoBlogger _prab_* meta → prcore JSON-LD Article emission); §4 version bumped to 0.8.0. peptide-repo-core.md — two new classes in §2 file tree; §3 data flow Mermaid extended with PRAB→MetaReader→Emitter chain; §5 cross-plugin dependency noted; §6 hooks table updated; last-verified bumped to v0.8.0. P1-C: JsonldArticleTest.php — add 5 new test methods: - test_standalone_fallback_emits_nothing_for_untriggered_post() - test_standalone_fallback_emits_valid_graph_for_triggered_post() - test_standalone_fallback_suppressed_when_yoast_active() - test_get_about_peptides_nonexistent_id_skipped() - test_get_about_peptides_wrong_post_type_skipped() Also add test_is_triggered_cache_returns_same_value() for P2-5 cache. P2-1: get_about_peptides() — skip entries where get_permalink() returns ''. P2-2: get_reviewed_by() — empty display_name falls through to Organization. P2-3: register_hooks() — explicit priority 10 on wpseo_schema_webpage_type. P2-4: emit_standalone_fallback() — guard wp_json_encode() false return; also guard empty permalink before building the graph. P2-5: is_triggered() — per-post avoids triple get_post_meta reads when all three hooks fire for the same post. P3s accepted (noted in QA verdict): P3-1 test-comment misleading; P3-2 docblock @see gap on emit_standalone_fallback; P3-3 DOI regex. Agent-Session: prcore-jsonld-remediation-20260623
485 lines
20 KiB
PHP
485 lines
20 KiB
PHP
<?php
|
|
/**
|
|
* Unit tests for PR_Core_Prab_Meta_Reader and PR_Core_Jsonld_Article.
|
|
*
|
|
* Coverage:
|
|
* - is_triggered: version=1 triggers, absent/wrong version does not.
|
|
* - is_triggered cache: second call returns cached value (no extra meta read).
|
|
* - get_review_mode: 'human', 'editorial-system', unknown → 'editorial-system'.
|
|
* - get_reviewed_by: human + valid user → Person; human + bad user → Org; editorial → Org.
|
|
* - get_reviewed_by: human + empty display_name → Org (P2-2 guard).
|
|
* - get_citations: valid, missing url, missing title, bad url, doi normalisation.
|
|
* - get_about_peptides: valid peptide ID; non-existent ID skipped; non-peptide type skipped.
|
|
* - get_reviewed_at: valid ISO, empty, unparseable → null.
|
|
* - retype_article_page: triggered post → MedicalWebPage; untriggered → passthrough.
|
|
* - enrich_article_graph: Article gains citation/about; WebPage gains lastReviewed/reviewedBy.
|
|
* - No duplicate Article/WebPage nodes emitted.
|
|
* - editorial-system mode never emits Person.
|
|
* - emit_standalone_fallback: Yoast-active suppresses output.
|
|
* - emit_standalone_fallback: untriggered post emits nothing.
|
|
* - emit_standalone_fallback: triggered post emits valid @graph with Article + MedicalWebPage.
|
|
*
|
|
* @package PeptideRepoCore\Tests
|
|
*/
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Tests for PRAB article JSON-LD meta-reader and emitter.
|
|
*/
|
|
class JsonldArticleTest extends TestCase {
|
|
|
|
protected function setUp(): void {
|
|
$GLOBALS['pr_test_postmeta'] = array();
|
|
$GLOBALS['pr_test_is_singular'] = false;
|
|
$GLOBALS['pr_test_singular_type'] = '';
|
|
$GLOBALS['pr_test_the_id'] = 0;
|
|
$GLOBALS['pr_test_posts'] = array();
|
|
$GLOBALS['pr_test_userdata'] = array();
|
|
$GLOBALS['pr_core_test_state'] = array(
|
|
'existing_post_types' => array(),
|
|
'existing_taxonomies' => array(),
|
|
'registered_post_types' => array(),
|
|
'registered_taxonomies' => array(),
|
|
'registered_meta' => array(),
|
|
'added_actions' => array(),
|
|
'added_filters' => array(),
|
|
);
|
|
|
|
require_once PR_CORE_PLUGIN_DIR . 'includes/cpt/class-pr-core-peptide-cpt.php';
|
|
require_once PR_CORE_PLUGIN_DIR . 'includes/frontend/class-pr-core-prab-meta-reader.php';
|
|
require_once PR_CORE_PLUGIN_DIR . 'includes/frontend/class-pr-core-jsonld-article.php';
|
|
}
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────────
|
|
|
|
/** @return PR_Core_Prab_Meta_Reader */
|
|
private function reader(): PR_Core_Prab_Meta_Reader {
|
|
return new PR_Core_Prab_Meta_Reader();
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $meta
|
|
*/
|
|
private function set_meta( int $post_id, array $meta ): void {
|
|
$GLOBALS['pr_test_postmeta'][ $post_id ] = $meta;
|
|
}
|
|
|
|
// ── is_triggered ─────────────────────────────────────────────────────
|
|
|
|
public function test_is_triggered_version_1(): void {
|
|
$this->set_meta( 1, array( '_prab_schema_version' => '1' ) );
|
|
$this->assertTrue( $this->reader()->is_triggered( 1 ) );
|
|
}
|
|
|
|
public function test_is_triggered_absent(): void {
|
|
$this->set_meta( 1, array() );
|
|
$this->assertFalse( $this->reader()->is_triggered( 1 ) );
|
|
}
|
|
|
|
public function test_is_triggered_unsupported_version(): void {
|
|
$this->set_meta( 1, array( '_prab_schema_version' => '2' ) );
|
|
$this->assertFalse( $this->reader()->is_triggered( 1 ) );
|
|
}
|
|
|
|
public function test_is_triggered_cache_returns_same_value(): void {
|
|
$this->set_meta( 5, array( '_prab_schema_version' => '1' ) );
|
|
$r = $this->reader();
|
|
$first = $r->is_triggered( 5 );
|
|
$second = $r->is_triggered( 5 );
|
|
$this->assertSame( $first, $second );
|
|
$this->assertTrue( $first );
|
|
}
|
|
|
|
// ── get_review_mode ───────────────────────────────────────────────────
|
|
|
|
public function test_review_mode_human(): void {
|
|
$this->set_meta( 1, array( '_prab_review_mode' => 'human' ) );
|
|
$this->assertSame( 'human', $this->reader()->get_review_mode( 1 ) );
|
|
}
|
|
|
|
public function test_review_mode_editorial_system(): void {
|
|
$this->set_meta( 1, array( '_prab_review_mode' => 'editorial-system' ) );
|
|
$this->assertSame( 'editorial-system', $this->reader()->get_review_mode( 1 ) );
|
|
}
|
|
|
|
public function test_review_mode_unknown_falls_back(): void {
|
|
$this->set_meta( 1, array( '_prab_review_mode' => 'robo-editor' ) );
|
|
$this->assertSame( 'editorial-system', $this->reader()->get_review_mode( 1 ) );
|
|
}
|
|
|
|
public function test_review_mode_empty_falls_back(): void {
|
|
$this->set_meta( 1, array() );
|
|
$this->assertSame( 'editorial-system', $this->reader()->get_review_mode( 1 ) );
|
|
}
|
|
|
|
// ── get_reviewed_by ───────────────────────────────────────────────────
|
|
|
|
public function test_reviewed_by_human_valid_user(): void {
|
|
$this->set_meta( 1, array( '_prab_reviewed_by' => '42' ) );
|
|
$GLOBALS['pr_test_userdata'][42] = new WP_User( array( 'display_name' => 'Dr Alice', 'ID' => 42 ) );
|
|
$node = $this->reader()->get_reviewed_by( 1, 'human' );
|
|
$this->assertSame( 'Person', $node['@type'] );
|
|
$this->assertSame( 'Dr Alice', $node['name'] );
|
|
$this->assertStringContainsString( '42', $node['@id'] );
|
|
}
|
|
|
|
public function test_reviewed_by_human_missing_user_returns_org(): void {
|
|
$this->set_meta( 1, array( '_prab_reviewed_by' => '99' ) );
|
|
// user 99 not in $GLOBALS['pr_test_userdata'] → get_userdata returns false.
|
|
$node = $this->reader()->get_reviewed_by( 1, 'human' );
|
|
$this->assertSame( 'Organization', $node['@type'] );
|
|
}
|
|
|
|
public function test_reviewed_by_human_zero_user_id_returns_org(): void {
|
|
$this->set_meta( 1, array( '_prab_reviewed_by' => '0' ) );
|
|
$node = $this->reader()->get_reviewed_by( 1, 'human' );
|
|
$this->assertSame( 'Organization', $node['@type'] );
|
|
}
|
|
|
|
public function test_reviewed_by_human_empty_display_name_returns_org(): void {
|
|
$this->set_meta( 1, array( '_prab_reviewed_by' => '77' ) );
|
|
$GLOBALS['pr_test_userdata'][77] = new WP_User( array( 'display_name' => '', 'ID' => 77 ) );
|
|
$node = $this->reader()->get_reviewed_by( 1, 'human' );
|
|
$this->assertSame( 'Organization', $node['@type'] );
|
|
}
|
|
|
|
public function test_reviewed_by_editorial_system_returns_org(): void {
|
|
$node = $this->reader()->get_reviewed_by( 1, 'editorial-system' );
|
|
$this->assertSame( 'Organization', $node['@type'] );
|
|
$this->assertSame( 'Peptide Repo', $node['name'] );
|
|
}
|
|
|
|
public function test_reviewed_by_org_never_person(): void {
|
|
// Ensure editorial-system never emits a Person even if reviewed_by meta present.
|
|
$this->set_meta( 1, array( '_prab_reviewed_by' => '42' ) );
|
|
$GLOBALS['pr_test_userdata'][42] = new WP_User( array( 'display_name' => 'Dr Alice', 'ID' => 42 ) );
|
|
$node = $this->reader()->get_reviewed_by( 1, 'editorial-system' );
|
|
$this->assertSame( 'Organization', $node['@type'] );
|
|
}
|
|
|
|
// ── get_citations ─────────────────────────────────────────────────────
|
|
|
|
public function test_citations_valid(): void {
|
|
$this->set_meta( 1, array(
|
|
'_prab_citations' => wp_json_encode( array(
|
|
array( 'url' => 'https://pubmed.ncbi.nlm.nih.gov/12345', 'title' => 'BPC-157 study' ),
|
|
array( 'url' => 'https://doi.org/10.1016/j.abc.2020.01', 'title' => 'RCT', 'doi' => '10.1016/j.abc.2020.01' ),
|
|
) ),
|
|
) );
|
|
$cits = $this->reader()->get_citations( 1 );
|
|
$this->assertCount( 2, $cits );
|
|
$this->assertSame( 'BPC-157 study', $cits[0]['title'] );
|
|
$this->assertNull( $cits[0]['doi'] );
|
|
$this->assertSame( 'https://doi.org/10.1016/j.abc.2020.01', $cits[1]['doi'] );
|
|
}
|
|
|
|
public function test_citations_missing_url_dropped(): void {
|
|
$this->set_meta( 1, array(
|
|
'_prab_citations' => wp_json_encode( array(
|
|
array( 'title' => 'No URL entry' ),
|
|
) ),
|
|
) );
|
|
$this->assertCount( 0, $this->reader()->get_citations( 1 ) );
|
|
}
|
|
|
|
public function test_citations_missing_title_dropped(): void {
|
|
$this->set_meta( 1, array(
|
|
'_prab_citations' => wp_json_encode( array(
|
|
array( 'url' => 'https://example.com' ),
|
|
) ),
|
|
) );
|
|
$this->assertCount( 0, $this->reader()->get_citations( 1 ) );
|
|
}
|
|
|
|
public function test_citations_invalid_url_scheme_dropped(): void {
|
|
$this->set_meta( 1, array(
|
|
'_prab_citations' => wp_json_encode( array(
|
|
array( 'url' => 'javascript:alert(1)', 'title' => 'XSS attempt' ),
|
|
) ),
|
|
) );
|
|
$this->assertCount( 0, $this->reader()->get_citations( 1 ) );
|
|
}
|
|
|
|
public function test_citations_bad_doi_emits_null_doi(): void {
|
|
$this->set_meta( 1, array(
|
|
'_prab_citations' => wp_json_encode( array(
|
|
array( 'url' => 'https://example.com', 'title' => 'Test', 'doi' => 'not-a-doi' ),
|
|
) ),
|
|
) );
|
|
$cits = $this->reader()->get_citations( 1 );
|
|
$this->assertCount( 1, $cits );
|
|
$this->assertNull( $cits[0]['doi'] );
|
|
}
|
|
|
|
public function test_citations_doi_normalised_from_uri(): void {
|
|
$this->set_meta( 1, array(
|
|
'_prab_citations' => wp_json_encode( array(
|
|
array( 'url' => 'https://example.com', 'title' => 'Test', 'doi' => 'https://doi.org/10.9999/x' ),
|
|
) ),
|
|
) );
|
|
$cits = $this->reader()->get_citations( 1 );
|
|
$this->assertSame( 'https://doi.org/10.9999/x', $cits[0]['doi'] );
|
|
}
|
|
|
|
public function test_citations_malformed_json_returns_empty(): void {
|
|
$this->set_meta( 1, array( '_prab_citations' => 'not-json' ) );
|
|
$this->assertCount( 0, $this->reader()->get_citations( 1 ) );
|
|
}
|
|
|
|
// ── get_about_peptides edge cases ─────────────────────────────────────
|
|
|
|
public function test_get_about_peptides_nonexistent_id_skipped(): void {
|
|
// post ID 999 does not exist in $GLOBALS['pr_test_posts'] → get_post returns null.
|
|
$this->set_meta( 1, array(
|
|
'_prab_schema_version' => '1',
|
|
'_prab_about_peptides' => wp_json_encode( array( 999 ) ),
|
|
) );
|
|
$stubs = $this->reader()->get_about_peptides( 1 );
|
|
$this->assertCount( 0, $stubs );
|
|
}
|
|
|
|
public function test_get_about_peptides_wrong_post_type_skipped(): void {
|
|
// A published post of type 'post' (not 'peptide') should be excluded.
|
|
$GLOBALS['pr_test_posts'][50] = new WP_Post( array(
|
|
'ID' => 50,
|
|
'post_type' => 'post',
|
|
'post_status' => 'publish',
|
|
'post_title' => 'Not a peptide',
|
|
) );
|
|
$this->set_meta( 1, array(
|
|
'_prab_schema_version' => '1',
|
|
'_prab_about_peptides' => wp_json_encode( array( 50 ) ),
|
|
) );
|
|
$stubs = $this->reader()->get_about_peptides( 1 );
|
|
$this->assertCount( 0, $stubs );
|
|
}
|
|
|
|
// ── get_reviewed_at ───────────────────────────────────────────────────
|
|
|
|
public function test_reviewed_at_valid_iso(): void {
|
|
$this->set_meta( 1, array( '_prab_reviewed_at' => '2026-06-11T08:00:00+00:00' ) );
|
|
$this->assertSame( '2026-06-11T08:00:00+00:00', $this->reader()->get_reviewed_at( 1 ) );
|
|
}
|
|
|
|
public function test_reviewed_at_empty_returns_null(): void {
|
|
$this->set_meta( 1, array() );
|
|
$this->assertNull( $this->reader()->get_reviewed_at( 1 ) );
|
|
}
|
|
|
|
public function test_reviewed_at_unparseable_returns_null(): void {
|
|
$this->set_meta( 1, array( '_prab_reviewed_at' => 'not-a-date' ) );
|
|
$this->assertNull( $this->reader()->get_reviewed_at( 1 ) );
|
|
}
|
|
|
|
// ── retype_article_page ───────────────────────────────────────────────
|
|
|
|
public function test_retype_triggered_post(): void {
|
|
$GLOBALS['pr_test_is_singular'] = true;
|
|
$GLOBALS['pr_test_singular_type'] = 'post';
|
|
$GLOBALS['pr_test_the_id'] = 1;
|
|
$this->set_meta( 1, array( '_prab_schema_version' => '1' ) );
|
|
$emitter = new PR_Core_Jsonld_Article();
|
|
$this->assertSame( 'MedicalWebPage', $emitter->retype_article_page( 'WebPage' ) );
|
|
}
|
|
|
|
public function test_retype_untriggered_passthrough(): void {
|
|
$GLOBALS['pr_test_is_singular'] = true;
|
|
$GLOBALS['pr_test_singular_type'] = 'post';
|
|
$GLOBALS['pr_test_the_id'] = 2;
|
|
$this->set_meta( 2, array() ); // no _prab_schema_version.
|
|
$emitter = new PR_Core_Jsonld_Article();
|
|
$this->assertSame( 'WebPage', $emitter->retype_article_page( 'WebPage' ) );
|
|
}
|
|
|
|
public function test_retype_non_post_passthrough(): void {
|
|
$GLOBALS['pr_test_is_singular'] = true;
|
|
$GLOBALS['pr_test_singular_type'] = 'peptide';
|
|
$GLOBALS['pr_test_the_id'] = 1;
|
|
$this->set_meta( 1, array( '_prab_schema_version' => '1' ) );
|
|
$emitter = new PR_Core_Jsonld_Article();
|
|
$this->assertSame( 'CollectionPage', $emitter->retype_article_page( 'CollectionPage' ) );
|
|
}
|
|
|
|
// ── enrich_article_graph ──────────────────────────────────────────────
|
|
|
|
public function test_enrich_adds_citation_and_about_to_article(): void {
|
|
$GLOBALS['pr_test_is_singular'] = true;
|
|
$GLOBALS['pr_test_singular_type'] = 'post';
|
|
$GLOBALS['pr_test_the_id'] = 1;
|
|
|
|
// Set up a published peptide post for about linkage.
|
|
$GLOBALS['pr_test_posts'][10] = new WP_Post( array(
|
|
'ID' => 10,
|
|
'post_type' => 'peptide',
|
|
'post_status' => 'publish',
|
|
'post_title' => 'BPC-157',
|
|
) );
|
|
|
|
$this->set_meta( 1, array(
|
|
'_prab_schema_version' => '1',
|
|
'_prab_citations' => wp_json_encode( array(
|
|
array( 'url' => 'https://pubmed.ncbi.nlm.nih.gov/1', 'title' => 'Study A' ),
|
|
) ),
|
|
'_prab_about_peptides' => wp_json_encode( array( 10 ) ),
|
|
'_prab_review_mode' => 'editorial-system',
|
|
'_prab_reviewed_at' => '2026-06-11T08:00:00+00:00',
|
|
) );
|
|
|
|
$graph = array(
|
|
array( '@type' => 'MedicalWebPage', '@id' => 'https://peptiderepo.com/post/1/#webpage' ),
|
|
array( '@type' => 'Article', '@id' => 'https://peptiderepo.com/post/1/#article' ),
|
|
);
|
|
|
|
$emitter = new PR_Core_Jsonld_Article();
|
|
$result_graph = $emitter->enrich_article_graph( $graph, null );
|
|
|
|
// Article should have citation and about.
|
|
$article = null;
|
|
$webpage = null;
|
|
foreach ( $result_graph as $piece ) {
|
|
if ( 'Article' === ( $piece['@type'] ?? '' ) ) {
|
|
$article = $piece;
|
|
}
|
|
if ( 'MedicalWebPage' === ( $piece['@type'] ?? '' ) ) {
|
|
$webpage = $piece;
|
|
}
|
|
}
|
|
|
|
$this->assertNotNull( $article, 'Article piece found in graph' );
|
|
$this->assertArrayHasKey( 'citation', $article );
|
|
$this->assertCount( 1, $article['citation'] );
|
|
$this->assertArrayHasKey( 'about', $article );
|
|
$this->assertCount( 1, $article['about'] );
|
|
$this->assertSame( 'Drug', $article['about'][0]['@type'] );
|
|
|
|
// WebPage should have lastReviewed and reviewedBy (Organization).
|
|
$this->assertNotNull( $webpage, 'MedicalWebPage piece found in graph' );
|
|
$this->assertArrayHasKey( 'lastReviewed', $webpage );
|
|
$this->assertSame( 'Organization', $webpage['reviewedBy']['@type'] );
|
|
}
|
|
|
|
public function test_enrich_untriggered_graph_unchanged(): void {
|
|
$GLOBALS['pr_test_is_singular'] = true;
|
|
$GLOBALS['pr_test_singular_type'] = 'post';
|
|
$GLOBALS['pr_test_the_id'] = 2;
|
|
$this->set_meta( 2, array() );
|
|
|
|
$graph = array(
|
|
array( '@type' => 'WebPage', '@id' => 'https://peptiderepo.com/post/2/#webpage' ),
|
|
array( '@type' => 'Article', '@id' => 'https://peptiderepo.com/post/2/#article' ),
|
|
);
|
|
$emitter = new PR_Core_Jsonld_Article();
|
|
$result = $emitter->enrich_article_graph( $graph, null );
|
|
|
|
// Graph must be identical — no new keys.
|
|
$this->assertSame( $graph, $result );
|
|
}
|
|
|
|
public function test_no_new_article_or_webpage_nodes_added(): void {
|
|
$GLOBALS['pr_test_is_singular'] = true;
|
|
$GLOBALS['pr_test_singular_type'] = 'post';
|
|
$GLOBALS['pr_test_the_id'] = 1;
|
|
$this->set_meta( 1, array( '_prab_schema_version' => '1', '_prab_review_mode' => 'editorial-system' ) );
|
|
|
|
$graph = array(
|
|
array( '@type' => 'MedicalWebPage', '@id' => 'x#w' ),
|
|
array( '@type' => 'Article', '@id' => 'x#a' ),
|
|
);
|
|
$emitter = new PR_Core_Jsonld_Article();
|
|
$result = $emitter->enrich_article_graph( $graph, null );
|
|
|
|
// Still exactly 2 pieces — no new nodes injected.
|
|
$this->assertCount( 2, $result );
|
|
}
|
|
|
|
// ── emit_standalone_fallback ──────────────────────────────────────────
|
|
|
|
public function test_standalone_fallback_emits_nothing_for_untriggered_post(): void {
|
|
$GLOBALS['pr_test_is_singular'] = true;
|
|
$GLOBALS['pr_test_singular_type'] = 'post';
|
|
$GLOBALS['pr_test_the_id'] = 3;
|
|
$this->set_meta( 3, array() ); // no _prab_schema_version.
|
|
|
|
$emitter = new PR_Core_Jsonld_Article();
|
|
ob_start();
|
|
$emitter->emit_standalone_fallback();
|
|
$output = ob_get_clean();
|
|
|
|
$this->assertSame( '', $output, 'Untriggered post must produce no output' );
|
|
}
|
|
|
|
public function test_standalone_fallback_emits_valid_graph_for_triggered_post(): void {
|
|
$GLOBALS['pr_test_is_singular'] = true;
|
|
$GLOBALS['pr_test_singular_type'] = 'post';
|
|
$GLOBALS['pr_test_the_id'] = 4;
|
|
$GLOBALS['pr_test_posts'][4] = new WP_Post( array(
|
|
'ID' => 4,
|
|
'post_type' => 'post',
|
|
'post_status' => 'publish',
|
|
'post_title' => 'BPC-157 Article',
|
|
) );
|
|
$this->set_meta( 4, array(
|
|
'_prab_schema_version' => '1',
|
|
'_prab_review_mode' => 'editorial-system',
|
|
'_prab_reviewed_at' => '2026-06-20T10:00:00+00:00',
|
|
'_prab_citations' => wp_json_encode( array(
|
|
array( 'url' => 'https://example.com/study', 'title' => 'Study One', 'doi' => '10.1234/abc' ),
|
|
) ),
|
|
) );
|
|
|
|
$emitter = new PR_Core_Jsonld_Article();
|
|
ob_start();
|
|
$emitter->emit_standalone_fallback();
|
|
$output = ob_get_clean();
|
|
|
|
// Must contain a script tag.
|
|
$this->assertStringContainsString( '<script type="application/ld+json">', $output );
|
|
$this->assertStringContainsString( '</script>', $output );
|
|
|
|
// Extract JSON between script tags.
|
|
preg_match( '#<script[^>]*>(.*?)</script>#s', $output, $matches );
|
|
$this->assertNotEmpty( $matches[1], 'JSON body found inside script tag' );
|
|
|
|
$decoded = json_decode( $matches[1], true );
|
|
$this->assertIsArray( $decoded, 'Output is valid JSON' );
|
|
$this->assertSame( 'https://schema.org', $decoded['@context'] );
|
|
$this->assertArrayHasKey( '@graph', $decoded );
|
|
|
|
// Collect types.
|
|
$types = array_column( $decoded['@graph'], '@type' );
|
|
$this->assertContains( 'MedicalWebPage', $types, '@graph contains MedicalWebPage' );
|
|
$this->assertContains( 'Article', $types, '@graph contains Article' );
|
|
|
|
// Article must have citation with ScholarlyArticle (DOI present).
|
|
$article_node = null;
|
|
foreach ( $decoded['@graph'] as $piece ) {
|
|
if ( 'Article' === ( $piece['@type'] ?? '' ) ) {
|
|
$article_node = $piece;
|
|
}
|
|
}
|
|
$this->assertNotNull( $article_node );
|
|
$this->assertArrayHasKey( 'citation', $article_node );
|
|
$this->assertSame( 'ScholarlyArticle', $article_node['citation'][0]['@type'] );
|
|
}
|
|
public function test_standalone_fallback_suppressed_when_yoast_active(): void {
|
|
// YoastSEO() is defined here so it runs LAST — once defined it persists for
|
|
// the whole PHPUnit process and would suppress the output assertions above.
|
|
if ( ! function_exists( 'YoastSEO' ) ) {
|
|
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound
|
|
eval( 'function YoastSEO() { return new stdClass(); }' );
|
|
}
|
|
$GLOBALS['pr_test_is_singular'] = true;
|
|
$GLOBALS['pr_test_singular_type'] = 'post';
|
|
$GLOBALS['pr_test_the_id'] = 1;
|
|
$this->set_meta( 1, array( '_prab_schema_version' => '1' ) );
|
|
|
|
$emitter = new PR_Core_Jsonld_Article();
|
|
ob_start();
|
|
$emitter->emit_standalone_fallback();
|
|
$output = ob_get_clean();
|
|
|
|
$this->assertSame( '', $output, 'Yoast-active path must produce no output' );
|
|
}
|
|
}
|