PluginProbe ʕ •ᴥ•ʔ
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More / 2.11.0
Reviews Feed – Add Testimonials and Customer Reviews From Google Reviews, Yelp, TripAdvisor, and More v2.11.0
2.11.0 2.10.0 2.9.0 2.8.0 2.7.0 2.6.7 2.6.8 2.6.5 2.6.4 2.6.3 2.6.2 2.6.0 2.5.5 2.5.4 2.5.3 2.5.2 trunk 1.0 1.0.1 1.0.2 1.0.3 1.1 1.1.1 1.1.2 1.2.0 2.0 2.1.0 2.1.1 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5.0 2.5.1
reviews-feed / tests / Unit / Smash782ReviewAlertProviderDataTest.php
reviews-feed / tests / Unit Last commit date
Doubles 1 week ago Providers 1 week ago BulkRearmOnGrowthTest.php 1 week ago BulkReviewsUpdateStuckStateTest.php 1 week ago ClearCacheRelayResetTest.php 1 week ago DeleteSourceRelayFailureTest.php 1 week ago ErrorHandlerFalsyOptionTest.php 1 week ago FeedCacheUpdateServiceTest.php 1 week ago FeedMalformedPayloadTest.php 1 week ago ForceKeylessRefetchTest.php 1 week ago LicenseDeactivateStaleStateTest.php 1 week ago MediaFinderMemoTest.php 1 week ago MultiSourceAggregationTest.php 1 week ago ReconcileMigratedLicenseRoutineTest.php 1 week ago ReconcileRemovalTest.php 1 week ago RegisterWebsiteRoutineTest.php 1 week ago RelaySlowEndpointsTest.php 1 week ago RemoteRequestMemoTest.php 1 week ago ReviewAlertHeaderTotalsTest.php 1 week ago ReviewAlertPageTargetingTest.php 1 week ago ReviewAlertStarFillTest.php 1 week ago ShortcodeNeutralizationTest.php 1 week ago SiteMigrationRecoveryTest.php 1 week ago Smash1130UsageTrackingHardeningTest.php 1 week ago Smash1130UsageTrackingHooksTest.php 1 week ago Smash1583HeaderParityTest.php 1 week ago Smash1631MultiLanguageBulkTest.php 1 week ago Smash1631UpdateSingleLangScopeTest.php 1 week ago Smash1706TripAdvisorPlaceIdTest.php 1 week ago Smash1756SchemaServiceTest.php 1 week ago Smash1785AvatarLocalUrlGuardTest.php 1 week ago Smash1785AvatarReHealTest.php 1 week ago Smash1795ReviewTextXssTest.php 1 week ago Smash1835TripAdvisorKeyShapeTest.php 1 week ago Smash1973WordpressOrgPlaceIdNullTest.php 1 week ago Smash1987NestedSourceErrorShapeTest.php 1 week ago Smash782BookingHeaderRatingTest.php 1 week ago Smash782CountryFlagEmojiTest.php 1 week ago Smash782ExternalRefreshCronTest.php 1 week ago Smash782ExtrasTemplateTest.php 1 week ago Smash782ReviewAlertProviderDataTest.php 1 week ago SourceIdLookupTest.php 1 week ago WpmlGetCurrentLanguageTest.php 1 week ago WpmlLanguageMappingTest.php 1 week ago
Smash782ReviewAlertProviderDataTest.php
242 lines
1 <?php
2
3 namespace SmashBalloon\Reviews\Tests\Unit;
4
5 use PHPUnit\Framework\TestCase;
6 use SmashBalloon\Reviews\Common\ReviewAlerts\SBR_Review_Alert_Frontend;
7
8 /**
9 * SMASH-782 — Review Alert provider-data pass-through (data layer).
10 *
11 * The alert popup renders the same provider-specific elements the feed does
12 * (Booking pros/cons + 0-10 score + helpful + photos, AliExpress variants /
13 * translated / buyer-flag / followup, Airbnb reply). Those all live in the
14 * review's `metadata` / `reply` / `title` / `reviewer_photos`, but the frontend
15 * formatter previously emitted only a whitelist (id/text/rating/time/reviewer/
16 * provider), so the popup never received them. This guards that the formatter
17 * now forwards the provider shape (additive — the original keys are unchanged).
18 *
19 * Reflection is used because format_reviews_for_frontend() is private and reads
20 * only its argument (no WP/DB state).
21 */
22 final class Smash782ReviewAlertProviderDataTest extends TestCase
23 {
24 /** @param array<int,array<string,mixed>> $reviews */
25 private function format(array $reviews): array
26 {
27 $fe = (new \ReflectionClass(SBR_Review_Alert_Frontend::class))->newInstanceWithoutConstructor();
28 $m = new \ReflectionMethod($fe, 'format_reviews_for_frontend');
29 $m->setAccessible(true);
30 return $m->invoke($fe, $reviews);
31 }
32
33 public function test_booking_score_and_word_are_resolved_server_side_from_the_raw_rating(): void
34 {
35 // The trap this pins: `rating` is cast to int for the star renderer, so a JS
36 // mirror that doubled it would publish 8.0 for a 4.5-star review while the
37 // feed card, the popup template and the JSON-LD all say 9.0. The formatter
38 // resolves the pair from the RAW rating before that cast happens.
39 $out = $this->format([[
40 'review_id' => 'b-half',
41 'text' => 'Lovely',
42 'rating' => 4.5,
43 'reviewer' => ['name' => 'Eva', 'avatar' => ''],
44 'provider' => ['name' => 'booking'],
45 ]]);
46 $r = $out[0];
47
48 $this->assertSame(9.0, $r['bookingScore']);
49 $this->assertSame('Superb', $r['bookingScoreWord']);
50 // The int cast on `rating` stays — the star renderer depends on it.
51 $this->assertSame(4, $r['rating']);
52 }
53
54 public function test_booking_score_ignores_the_property_wide_metadata_score(): void
55 {
56 // metadata.review_score is the HOTEL's score, identical on every card. Reading
57 // it here is what made the popup show one score no matter who was cycled in.
58 $out = $this->format([[
59 'review_id' => 'b2',
60 'text' => 'Fine',
61 'rating' => 4,
62 'reviewer' => ['name' => 'Dan', 'avatar' => ''],
63 'provider' => ['name' => 'booking'],
64 'metadata' => ['review_score' => 9.5, 'review_score_word' => 'Exceptional'],
65 ]]);
66 $r = $out[0];
67
68 $this->assertSame(8.0, $r['bookingScore']);
69 $this->assertSame('Very good', $r['bookingScoreWord']);
70 }
71
72 public function test_preview_and_frontend_resolve_a_booking_score_from_the_same_basis(): void
73 {
74 // The builder preview computes the badge itself (SbUtils.bookingReviewScore →
75 // rating × 2) because there is no PHP round-trip in the editor, so it needs the
76 // same rating the frontend resolves from. get_preview_reviews() used to ship
77 // `(int) $rating`: a 4.5 review rendered "8.0 Very good" in the popup editor and
78 // "9.0 Superb" in the live popup. Ten reviewers in the local cache sit at 4.5, so
79 // this was live, not hypothetical.
80 //
81 // Reflection on the shaping expression only — get_preview_reviews() itself needs
82 // the Feed/DB stack. What this pins is that the two sides agree on the number.
83 $review = [
84 'review_id' => 'b-half',
85 'text' => 'Lovely',
86 'rating' => 4.5,
87 'reviewer' => ['name' => 'Adrian', 'avatar' => ''],
88 'provider' => ['name' => 'booking'],
89 ];
90
91 $frontend = $this->format([$review])[0];
92 $this->assertSame(9.0, $frontend['bookingScore'], 'frontend basis');
93 $this->assertSame('Superb', $frontend['bookingScoreWord']);
94
95 // The preview ships the rating itself; both must double to the same score.
96 $preview_rating = $this->preview_rating_for('booking', $review);
97 $this->assertSame(4.5, $preview_rating, 'preview must not truncate a Booking rating');
98 $this->assertSame(
99 $frontend['bookingScore'],
100 sbr_booking_review_score(['rating' => $preview_rating]),
101 'preview and frontend must land on the same 0-10 score'
102 );
103
104 // Non-Booking keeps the int cast the star renderer depends on.
105 $this->assertSame(4, $this->preview_rating_for('google', ['rating' => 4.5]));
106 }
107
108 /**
109 * Mirror of the rating expression in SBR_Review_Alert_Service::get_preview_reviews().
110 * Kept in step by the guard below, which fails if that line stops sending a float.
111 *
112 * @param array<string,mixed> $review
113 * @return float|int
114 */
115 private function preview_rating_for(string $provider, array $review)
116 {
117 return 'booking' === $provider
118 ? (float) ($review['rating'] ?? 0)
119 : (int) $review['rating'];
120 }
121
122 public function test_the_preview_payload_sends_booking_ratings_unrounded(): void
123 {
124 // Guards the mirror above: get_preview_reviews() needs the Feed/DB stack, so
125 // there is no DB-free way to call it and the source is what gets pinned. Without
126 // this, dropping the Booking branch leaves the test above green while the preview
127 // silently drifts a whole band from the live popup.
128 //
129 // The CAST is the load-bearing part, not just the branch — `(int)` inside the
130 // same ternary would truncate exactly as before — so the pattern requires it.
131 $src = (string) file_get_contents(
132 dirname(__DIR__, 2) . '/class/Common/ReviewAlerts/SBR_Review_Alert_Service.php'
133 );
134
135 $this->assertMatchesRegularExpression(
136 "/'rating'\s*=>\s*'booking'\s*===\s*\\\$review_provider\s*\?\s*\(float\)/",
137 $src,
138 'get_preview_reviews() must send Booking ratings as a float — see the test above.'
139 );
140 // And nothing may re-truncate them on the Booking arm.
141 $this->assertDoesNotMatchRegularExpression(
142 "/'rating'\s*=>\s*'booking'\s*===\s*\\\$review_provider\s*\?\s*\(int\)/",
143 $src
144 );
145 }
146
147 public function test_non_booking_reviews_carry_no_booking_score_keys(): void
148 {
149 $out = $this->format([[
150 'review_id' => 'g1',
151 'text' => 'Nice',
152 'rating' => 5,
153 'reviewer' => ['name' => 'Gina', 'avatar' => ''],
154 'provider' => ['name' => 'google'],
155 ]]);
156
157 $this->assertArrayNotHasKey('bookingScore', $out[0]);
158 $this->assertArrayNotHasKey('bookingScoreWord', $out[0]);
159 }
160
161 public function test_booking_review_forwards_metadata_title_and_photos(): void
162 {
163 $out = $this->format([[
164 'review_id' => 'b1',
165 'text' => 'Great stay',
166 'title' => 'It was excellent',
167 'rating' => 5,
168 'reviewer' => ['name' => 'Alison', 'avatar' => ''],
169 'provider' => ['name' => 'booking'],
170 'metadata' => [
171 'pros' => 'Spacious room',
172 'cons' => 'Pricey breakfast',
173 'review_score' => 8.6,
174 'review_score_word' => 'Fabulous',
175 'helpful_vote_count' => 14,
176 ],
177 'reviewer_photos' => [['90_90' => 'https://x/a.jpg']],
178 'source' => ['id' => '1377073'],
179 ]]);
180 $r = $out[0];
181 $this->assertSame('It was excellent', $r['title']);
182 $this->assertSame('Spacious room', $r['metadata']['pros']);
183 $this->assertSame('Pricey breakfast', $r['metadata']['cons']);
184 $this->assertSame(8.6, $r['metadata']['review_score']);
185 $this->assertSame(14, $r['metadata']['helpful_vote_count']);
186 $this->assertNotEmpty($r['reviewer_photos']);
187 $this->assertSame('1377073', $r['source']['id']);
188 }
189
190 public function test_aliexpress_review_forwards_variants_translated_flag(): void
191 {
192 $out = $this->format([[
193 'text' => 'Nice shirt',
194 'rating' => 4,
195 'reviewer' => ['name' => 'Shopper'],
196 'provider' => ['name' => 'aliexpress'],
197 'metadata' => [
198 'item_spec' => 'Color:Black Size:XL',
199 'translated' => true,
200 'buyer_country' => 'US',
201 ],
202 ]]);
203 $md = $out[0]['metadata'];
204 $this->assertSame('Color:Black Size:XL', $md['item_spec']);
205 $this->assertTrue($md['translated']);
206 $this->assertSame('US', $md['buyer_country']);
207 }
208
209 public function test_airbnb_review_forwards_reply(): void
210 {
211 $out = $this->format([[
212 'text' => 'Lovely place',
213 'rating' => 5,
214 'reviewer' => ['name' => 'Jamie'],
215 'provider' => ['name' => 'airbnb'],
216 'response' => 'Thanks for staying!',
217 'reply' => ['name' => 'Host', 'avatar' => ''],
218 ]]);
219 $this->assertSame('Thanks for staying!', $out[0]['response']);
220 $this->assertSame('Host', $out[0]['reply']['name']);
221 }
222
223 public function test_missing_provider_data_degrades_to_safe_empties_bc(): void
224 {
225 // A Google/legacy review with none of the new keys must not error and
226 // must keep the original contract intact.
227 $out = $this->format([[
228 'text' => 'Good',
229 'rating' => 5,
230 'reviewer' => ['name' => 'Sam'],
231 'provider' => ['name' => 'google'],
232 ]]);
233 $r = $out[0];
234 $this->assertSame('Good', $r['text']);
235 $this->assertSame([], $r['metadata']);
236 $this->assertSame([], $r['reply']);
237 $this->assertSame('', $r['response']);
238 $this->assertSame([], $r['reviewer_photos']);
239 $this->assertSame('', $r['title']);
240 }
241 }
242