Doubles
1 month ago
Providers
1 month ago
BulkRearmOnGrowthTest.php
1 month ago
BulkReviewsUpdateStuckStateTest.php
1 month ago
ClearCacheRelayResetTest.php
1 month ago
DeleteSourceRelayFailureTest.php
1 month ago
ErrorHandlerFalsyOptionTest.php
1 month ago
FeedCacheUpdateServiceTest.php
1 month ago
FeedMalformedPayloadTest.php
1 month ago
ForceKeylessRefetchTest.php
1 month ago
LicenseDeactivateStaleStateTest.php
1 month ago
MediaFinderMemoTest.php
1 month ago
MultiSourceAggregationTest.php
1 month ago
ReconcileMigratedLicenseRoutineTest.php
1 month ago
ReconcileRemovalTest.php
1 month ago
RegisterWebsiteRoutineTest.php
1 month ago
RemoteRequestMemoTest.php
1 month ago
ReviewAlertHeaderTotalsTest.php
1 month ago
ReviewAlertPageTargetingTest.php
1 month ago
ReviewAlertStarFillTest.php
1 month ago
ShortcodeNeutralizationTest.php
1 month ago
SiteMigrationRecoveryTest.php
1 month ago
Smash1583HeaderParityTest.php
1 month ago
Smash1631MultiLanguageBulkTest.php
1 month ago
Smash1631UpdateSingleLangScopeTest.php
1 month ago
Smash1706TripAdvisorPlaceIdTest.php
1 month ago
Smash782BookingHeaderRatingTest.php
1 month ago
Smash782CountryFlagEmojiTest.php
1 month ago
Smash782ExternalRefreshCronTest.php
1 month ago
Smash782ExtrasTemplateTest.php
1 month ago
Smash782ReviewAlertProviderDataTest.php
1 month ago
SourceIdLookupTest.php
1 month ago
WpmlGetCurrentLanguageTest.php
1 month ago
WpmlLanguageMappingTest.php
1 month ago
Smash782ExtrasTemplateTest.php
267 lines
| 1 | <?php |
| 2 | |
| 3 | // Stub WP escape + i18n helpers in the GLOBAL namespace so plugin templates |
| 4 | // (which call `esc_html`, `esc_attr`, etc. as global functions) can resolve |
| 5 | // them when included from these unit tests. The plugin's tests/bootstrap.php |
| 6 | // already stubs sanitize_text_field / __() / etc., but not these. |
| 7 | namespace { |
| 8 | if (!function_exists('esc_html')) { |
| 9 | function esc_html($s) |
| 10 | { |
| 11 | return htmlspecialchars((string) $s, ENT_QUOTES, 'UTF-8'); |
| 12 | } |
| 13 | } |
| 14 | if (!function_exists('esc_attr')) { |
| 15 | function esc_attr($s) |
| 16 | { |
| 17 | return htmlspecialchars((string) $s, ENT_QUOTES, 'UTF-8'); |
| 18 | } |
| 19 | } |
| 20 | if (!function_exists('esc_url')) { |
| 21 | function esc_url($url) |
| 22 | { |
| 23 | return (string) $url; |
| 24 | } |
| 25 | } |
| 26 | if (!function_exists('esc_html__')) { |
| 27 | function esc_html__($text, $domain = null) |
| 28 | { |
| 29 | return esc_html($text); |
| 30 | } |
| 31 | } |
| 32 | if (!function_exists('_n')) { |
| 33 | function _n($single, $plural, $number, $domain = null) |
| 34 | { |
| 35 | return (int) $number === 1 ? $single : $plural; |
| 36 | } |
| 37 | } |
| 38 | if (!function_exists('wp_kses_post')) { |
| 39 | function wp_kses_post($html) |
| 40 | { |
| 41 | return $html; |
| 42 | } |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | namespace SbReviews\Tests\Unit { |
| 47 | |
| 48 | use PHPUnit\Framework\TestCase; |
| 49 | |
| 50 | /** |
| 51 | * SMASH-782 Phase 2 — plugin template render tests. |
| 52 | * |
| 53 | * Each of the new per-provider templates (extras + rating-extras + |
| 54 | * text-aliexpress) is pure PHP that takes `$post` and outputs HTML. |
| 55 | * These tests render the templates with fixtures mirroring real relay |
| 56 | * output and assert the HTML contains the prototype-required classes |
| 57 | * AND that templates emit NOTHING when real data is missing |
| 58 | * (no demo / static fallback content). |
| 59 | */ |
| 60 | class Smash782ExtrasTemplateTest extends TestCase |
| 61 | { |
| 62 | private const TEMPLATES_DIR = __DIR__ . '/../../templates/frontend/post-elements'; |
| 63 | |
| 64 | /** |
| 65 | * Render a template with `$post` in scope and capture output. |
| 66 | * |
| 67 | * PHPUnit 10 with `beStrictAboutOutputDuringTests="true"` tracks output |
| 68 | * buffer count and fails if buffers we open aren't closed. The level guard |
| 69 | * below force-closes any buffer left open by the template (even if it |
| 70 | * returned early through an exception path), so PHPUnit's count matches. |
| 71 | */ |
| 72 | private function render(string $relPath, array $post): string |
| 73 | { |
| 74 | $startLevel = ob_get_level(); |
| 75 | ob_start(); |
| 76 | try { |
| 77 | include self::TEMPLATES_DIR . '/' . $relPath; |
| 78 | } finally { |
| 79 | $out = ''; |
| 80 | while (ob_get_level() > $startLevel) { |
| 81 | $out = ob_get_clean() . $out; |
| 82 | } |
| 83 | } |
| 84 | return $out; |
| 85 | } |
| 86 | |
| 87 | // ------------------------------------------------------------------------- |
| 88 | // AIRBNB extras |
| 89 | // ------------------------------------------------------------------------- |
| 90 | |
| 91 | public function test_airbnb_reply_renders_full_block_when_response_present(): void |
| 92 | { |
| 93 | $out = $this->render('extras/airbnb.php', [ |
| 94 | 'response' => 'Thanks for staying!', |
| 95 | 'reply' => [ |
| 96 | 'name' => 'Bill', |
| 97 | 'avatar' => 'https://muscache/host.jpg', |
| 98 | ], |
| 99 | 'provider' => ['name' => 'airbnb'], |
| 100 | ]); |
| 101 | |
| 102 | $this->assertStringContainsString('class="sb-item-reply"', $out); |
| 103 | $this->assertStringContainsString('data-from="host"', $out); |
| 104 | $this->assertStringContainsString('class="sb-item-reply-header"', $out); |
| 105 | $this->assertStringContainsString('<img class="sb-item-reply-avatar"', $out); |
| 106 | $this->assertStringContainsString('src="https://muscache/host.jpg"', $out); |
| 107 | $this->assertStringContainsString('class="sb-item-reply-name">Bill', $out); |
| 108 | $this->assertStringContainsString('class="sb-item-reply-label">Host', $out); |
| 109 | $this->assertStringContainsString('class="sb-item-reply-text">Thanks for staying!', $out); |
| 110 | } |
| 111 | |
| 112 | public function test_airbnb_reply_emits_nothing_when_response_empty(): void |
| 113 | { |
| 114 | $out = $this->render('extras/airbnb.php', [ |
| 115 | 'response' => '', |
| 116 | 'reply' => ['name' => 'Bill', 'avatar' => 'https://x.jpg'], |
| 117 | 'provider' => ['name' => 'airbnb'], |
| 118 | ]); |
| 119 | $this->assertSame('', trim($out), 'No demo fallback — empty response must produce empty output.'); |
| 120 | } |
| 121 | |
| 122 | public function test_airbnb_reply_renders_without_avatar_when_only_text(): void |
| 123 | { |
| 124 | $out = $this->render('extras/airbnb.php', [ |
| 125 | 'response' => 'Thanks!', |
| 126 | 'reply' => ['name' => '', 'avatar' => ''], |
| 127 | 'provider' => ['name' => 'airbnb'], |
| 128 | ]); |
| 129 | $this->assertStringContainsString('class="sb-item-reply"', $out); |
| 130 | $this->assertStringContainsString('class="sb-item-reply-text">Thanks!', $out); |
| 131 | $this->assertStringNotContainsString('sb-item-reply-avatar', $out); |
| 132 | $this->assertStringNotContainsString('sb-item-reply-name', $out); |
| 133 | } |
| 134 | |
| 135 | // ------------------------------------------------------------------------- |
| 136 | // BOOKING extras + rating-extras |
| 137 | // ------------------------------------------------------------------------- |
| 138 | |
| 139 | public function test_booking_rating_extras_renders_score_badge_inside_rating_slot(): void |
| 140 | { |
| 141 | // Booking's REAL 0-10 hotel score + word, forwarded by the relay as |
| 142 | // metadata.review_score / metadata.review_score_word (no derived value, |
| 143 | // no invented bands — see rating-extras/booking.php docblock). |
| 144 | $out = $this->render('rating-extras/booking.php', [ |
| 145 | 'metadata' => ['review_score' => 8.7, 'review_score_word' => 'Very Good'], |
| 146 | 'provider' => ['name' => 'booking'], |
| 147 | ]); |
| 148 | $this->assertStringContainsString('class="sb-item-rating-score"', $out); |
| 149 | $this->assertStringContainsString('class="sb-item-rating-score-badge">8.7', $out); |
| 150 | $this->assertStringContainsString('class="sb-item-rating-score-label">Very Good', $out); |
| 151 | } |
| 152 | |
| 153 | public function test_booking_rating_extras_emits_nothing_when_rating_zero(): void |
| 154 | { |
| 155 | $out = $this->render('rating-extras/booking.php', [ |
| 156 | 'rating' => 0, |
| 157 | 'provider' => ['name' => 'booking'], |
| 158 | ]); |
| 159 | $this->assertSame('', trim($out)); |
| 160 | } |
| 161 | |
| 162 | public function test_booking_score_renders_real_metadata_score_and_word_verbatim(): void |
| 163 | { |
| 164 | // The badge shows Booking's real 0-10 score verbatim (number_format 1dp) |
| 165 | // and the label shows Booking's own word verbatim — never a value/band |
| 166 | // derived from the 0-5 star rating. |
| 167 | $cases = [ |
| 168 | ['score' => 10.0, 'word' => 'Exceptional'], |
| 169 | ['score' => 8.5, 'word' => 'Very Good'], |
| 170 | ['score' => 7.5, 'word' => 'Good'], |
| 171 | ['score' => 6.5, 'word' => 'Pleasant'], |
| 172 | ]; |
| 173 | foreach ($cases as $c) { |
| 174 | $out = $this->render('rating-extras/booking.php', [ |
| 175 | 'metadata' => ['review_score' => $c['score'], 'review_score_word' => $c['word']], |
| 176 | ]); |
| 177 | $this->assertStringContainsString('>' . number_format($c['score'], 1) . '<', $out, 'score ' . $c['score']); |
| 178 | $this->assertStringContainsString($c['word'], $out, 'score ' . $c['score']); |
| 179 | } |
| 180 | |
| 181 | // Real score present but no word from the relay -> badge only, no label. |
| 182 | $out = $this->render('rating-extras/booking.php', [ |
| 183 | 'metadata' => ['review_score' => 8.0], |
| 184 | ]); |
| 185 | $this->assertStringContainsString('>8.0<', $out); |
| 186 | $this->assertStringNotContainsString('rating-score-label', $out); |
| 187 | } |
| 188 | |
| 189 | public function test_booking_extras_renders_helpful_when_count_positive(): void |
| 190 | { |
| 191 | $out = $this->render('extras/booking.php', [ |
| 192 | 'metadata' => ['helpful_vote_count' => 14], |
| 193 | 'provider' => ['name' => 'booking'], |
| 194 | ]); |
| 195 | // class list also carries the SMASH-782 section-spacing class |
| 196 | // (sbr-review-horizontal-element), so match the class prefix only. |
| 197 | $this->assertStringContainsString('class="sb-item-helpful', $out); |
| 198 | $this->assertStringContainsString('class="sb-item-helpful-icon"', $out); |
| 199 | $this->assertStringContainsString('14 people found this helpful', $out); |
| 200 | } |
| 201 | |
| 202 | public function test_booking_extras_renders_singular_helpful(): void |
| 203 | { |
| 204 | $out = $this->render('extras/booking.php', [ |
| 205 | 'metadata' => ['helpful_vote_count' => 1], |
| 206 | 'provider' => ['name' => 'booking'], |
| 207 | ]); |
| 208 | $this->assertStringContainsString('1 person found this helpful', $out); |
| 209 | } |
| 210 | |
| 211 | public function test_booking_extras_emits_nothing_when_helpful_zero(): void |
| 212 | { |
| 213 | $out = $this->render('extras/booking.php', [ |
| 214 | 'metadata' => ['helpful_vote_count' => 0], |
| 215 | 'provider' => ['name' => 'booking'], |
| 216 | ]); |
| 217 | $this->assertSame('', trim($out)); |
| 218 | } |
| 219 | |
| 220 | // ------------------------------------------------------------------------- |
| 221 | // ALIEXPRESS extras + text-aliexpress |
| 222 | // ------------------------------------------------------------------------- |
| 223 | |
| 224 | public function test_aliexpress_extras_renders_variants_only_when_item_spec_present(): void |
| 225 | { |
| 226 | $out = $this->render('extras/aliexpress.php', [ |
| 227 | 'metadata' => ['item_spec' => 'Color:Black Size:XL'], |
| 228 | 'provider' => ['name' => 'aliexpress'], |
| 229 | ]); |
| 230 | $this->assertStringContainsString('class="sb-item-variants', $out); |
| 231 | $this->assertStringContainsString('class="sb-item-variants-pill">Color: Black</span>', $out); |
| 232 | $this->assertStringContainsString('class="sb-item-variants-pill">Size: XL</span>', $out); |
| 233 | } |
| 234 | |
| 235 | public function test_aliexpress_extras_emits_nothing_when_no_variants_no_followup(): void |
| 236 | { |
| 237 | $out = $this->render('extras/aliexpress.php', [ |
| 238 | 'metadata' => ['item_spec' => '', 'followup' => null], |
| 239 | 'provider' => ['name' => 'aliexpress'], |
| 240 | ]); |
| 241 | $this->assertSame('', trim($out)); |
| 242 | } |
| 243 | |
| 244 | public function test_aliexpress_extras_renders_followup_block_when_metadata_populated(): void |
| 245 | { |
| 246 | $out = $this->render('extras/aliexpress.php', [ |
| 247 | 'metadata' => [ |
| 248 | 'item_spec' => '', |
| 249 | 'followup' => [ |
| 250 | 'date' => '2 weeks later', |
| 251 | 'text' => 'Still going strong.', |
| 252 | ], |
| 253 | ], |
| 254 | 'provider' => ['name' => 'aliexpress'], |
| 255 | ]); |
| 256 | |
| 257 | // class list also carries the SMASH-782 section-spacing class, so match prefix only. |
| 258 | $this->assertStringContainsString('class="sb-item-followup', $out); |
| 259 | $this->assertStringContainsString('class="sb-item-followup-header"', $out); |
| 260 | $this->assertStringContainsString('class="sb-item-followup-label">Follow-up Review', $out); |
| 261 | $this->assertStringContainsString('class="sb-item-followup-date">2 weeks later', $out); |
| 262 | $this->assertStringContainsString('class="sb-item-followup-text">Still going strong.', $out); |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | } // end namespace SbReviews\Tests\Unit |
| 267 |