@@ -172,4 +172,160 @@ public function test_does_not_throw_deprecation_notice_for_rtrim_with_default_pa
172172 $ expected = file_get_contents ( DIR_TESTDATA . '/languages/en_US-813e104eb47e13dd4cc5af844c618754.json ' );
173173 $ this ->assertSame ( $ expected , load_script_textdomain ( $ handle ) );
174174 }
175+
176+ /**
177+ * Records every `$file` value passed to `load_script_translations()`
178+ * so individual tests can assert which code path produced the result.
179+ *
180+ * @return list<string|false> Reference to the array updated by the spy filter.
181+ */
182+ private function &spy_load_script_translations_files (): array {
183+ /** @var list<string|false> $files_seen */
184+ $ files_seen = array ();
185+ add_filter (
186+ 'pre_load_script_translations ' ,
187+ static function ( $ translations , $ file ) use ( &$ files_seen ) {
188+ assert ( is_string ( $ file ) || false === $ file );
189+ $ files_seen [] = $ file ;
190+ return $ translations ;
191+ },
192+ 10 ,
193+ 2
194+ );
195+ return $ files_seen ;
196+ }
197+
198+ /**
199+ * Tests that an unparseable script source URL short-circuits to
200+ * `load_script_translations( false, ... )` instead of falling through
201+ * to the relative-path computation.
202+ *
203+ * @ticket 65015
204+ */
205+ public function test_unparseable_src_returns_false (): void {
206+ $ handle = 'test-unparseable-src ' ;
207+ $ src = 'http:///example ' ;
208+
209+ $ this ->assertFalse ( wp_parse_url ( $ src ), 'Test prerequisite failed: the test src should be unparseable. ' );
210+
211+ $ files_seen = &$ this ->spy_load_script_translations_files ();
212+
213+ wp_enqueue_script ( $ handle , $ src , array (), null );
214+
215+ $ this ->assertFalse ( load_script_textdomain ( $ handle , 'default ' , DIR_TESTDATA . '/languages ' ) );
216+ $ this ->assertSame (
217+ array (
218+ DIR_TESTDATA . '/languages/en_US- ' . $ handle . '.json ' ,
219+ false ,
220+ ),
221+ $ files_seen ,
222+ 'Expected the unparseable $src branch to short-circuit before any relative-path lookup. '
223+ );
224+ }
225+
226+ /**
227+ * Tests that an unparseable `content_url()` return value short-circuits
228+ * to `load_script_translations( false, ... )` instead of computing
229+ * `$relative` from a corrupted parsed-URL array.
230+ *
231+ * @ticket 65015
232+ */
233+ public function test_unparseable_content_url_returns_false (): void {
234+ $ handle = 'test-unparseable-content-url ' ;
235+ $ src = '/wp-includes/js/script.js ' ;
236+
237+ add_filter (
238+ 'content_url ' ,
239+ static function () {
240+ return 'http:///example ' ;
241+ }
242+ );
243+
244+ $ files_seen = &$ this ->spy_load_script_translations_files ();
245+
246+ wp_enqueue_script ( $ handle , $ src , array (), null );
247+
248+ $ this ->assertFalse ( load_script_textdomain ( $ handle , 'default ' , DIR_TESTDATA . '/languages ' ) );
249+ $ this ->assertSame (
250+ array (
251+ DIR_TESTDATA . '/languages/en_US- ' . $ handle . '.json ' ,
252+ false ,
253+ ),
254+ $ files_seen ,
255+ 'Expected the unparseable content_url branch to short-circuit before any relative-path lookup. '
256+ );
257+ }
258+
259+ /**
260+ * Tests that the `load_script_textdomain_relative_path` filter returning
261+ * a non-string, non-false value short-circuits via the
262+ * `! is_string( $relative )` guard rather than falling through to
263+ * string functions like `str_ends_with()` and `md5()`.
264+ *
265+ * @ticket 65015
266+ *
267+ * @dataProvider data_non_string_relative_path_filter_values
268+ *
269+ * @param mixed $filter_value Value returned from the filter.
270+ */
271+ public function test_non_string_relative_path_filter_returns_false ( $ filter_value ): void {
272+ $ handle = 'test-non-string-relative-path ' ;
273+ $ src = '/wp-includes/js/script.js ' ;
274+
275+ add_filter (
276+ 'load_script_textdomain_relative_path ' ,
277+ static function () use ( $ filter_value ) {
278+ return $ filter_value ;
279+ }
280+ );
281+
282+ $ files_seen = &$ this ->spy_load_script_translations_files ();
283+
284+ wp_enqueue_script ( $ handle , $ src , array (), null );
285+
286+ $ this ->assertFalse ( load_script_textdomain ( $ handle , 'default ' , DIR_TESTDATA . '/languages ' ) );
287+ $ this ->assertSame (
288+ array (
289+ DIR_TESTDATA . '/languages/en_US- ' . $ handle . '.json ' ,
290+ false ,
291+ ),
292+ $ files_seen ,
293+ 'Expected the non-string $relative branch to short-circuit before md5 path computation. '
294+ );
295+ }
296+
297+ /**
298+ * Provides data for {@see self::test_non_string_relative_path_filter_returns_false()}.
299+ *
300+ * @return array<string, array{0: mixed}>
301+ */
302+ public static function data_non_string_relative_path_filter_values (): array {
303+ return array (
304+ 'null ' => array ( null ),
305+ 'true ' => array ( true ),
306+ 'array ' => array ( array ( 'wp-includes/js/script.js ' ) ),
307+ 'int ' => array ( 0 ),
308+ );
309+ }
310+
311+ /**
312+ * Tests that a script source URL with no path component does not trigger
313+ * an undefined index warning when the path is read further down in the
314+ * function. The result is reached via the regular fallback path
315+ * (no host/path match) rather than an early return.
316+ *
317+ * @ticket 65015
318+ */
319+ public function test_src_without_path_component_does_not_warn (): void {
320+ $ handle = 'test-src-without-path ' ;
321+ $ src = 'https://example.com ' ;
322+
323+ $ parsed = wp_parse_url ( $ src );
324+ $ this ->assertIsArray ( $ parsed , 'Test prerequisite failed: the test src should parse. ' );
325+ $ this ->assertArrayNotHasKey ( 'path ' , $ parsed , 'Test prerequisite failed: the test src should have no path component. ' );
326+
327+ wp_enqueue_script ( $ handle , $ src , array (), null );
328+
329+ $ this ->assertFalse ( load_script_textdomain ( $ handle , 'default ' , DIR_TESTDATA . '/languages ' ) );
330+ }
175331}
0 commit comments