@@ -73,6 +73,132 @@ public function testScannerBundleUrlIncludesVersionQueryString(): void {
7373 $ this ->assertStringContainsString ( 'ver= ' . EDAC_VERSION , $ localized_data );
7474 }
7575
76+ /**
77+ * Helper: enqueue the frontend highlighter as an admin and return the localized data string.
78+ *
79+ * @return string The raw JS localized-data string for edac-frontend-highlighter-app.
80+ */
81+ private function enqueueAndGetLocalizedData (): string {
82+ $ admin_id = $ this ->factory ()->user ->create ( [ 'role ' => 'administrator ' ] );
83+ wp_set_current_user ( $ admin_id );
84+
85+ global $ post ;
86+ $ post = $ this ->factory ()->post ->create_and_get ( [ 'post_type ' => 'post ' ] );
87+
88+ Enqueue_Frontend::maybe_enqueue_frontend_highlighter ();
89+
90+ global $ wp_scripts ;
91+ return (string ) $ wp_scripts ->get_data ( 'edac-frontend-highlighter-app ' , 'data ' );
92+ }
93+
94+ /**
95+ * RestUrl is present in the localized data passed to the frontend highlighter script.
96+ */
97+ public function testLocalizedDataIncludesRestUrl (): void {
98+ $ localized_data = $ this ->enqueueAndGetLocalizedData ();
99+
100+ $ this ->assertNotEmpty ( $ localized_data );
101+ $ this ->assertStringContainsString ( 'restUrl ' , $ localized_data );
102+ }
103+
104+ /**
105+ * RestUrl uses the accessibility-checker/v1 namespace and matches rest_url().
106+ */
107+ public function testRestUrlMatchesRestUrlFunction (): void {
108+ $ localized_data = $ this ->enqueueAndGetLocalizedData ();
109+ $ expected = rest_url ( 'accessibility-checker/v1 ' );
110+
111+ $ this ->assertStringContainsString ( 'accessibility-checker ' , $ localized_data );
112+ $ this ->assertStringContainsString ( 'v1 ' , $ localized_data );
113+ // The URL must be derived from rest_url(), not hardcoded — verify the host is present.
114+ $ this ->assertStringContainsString ( (string ) wp_parse_url ( $ expected , PHP_URL_HOST ), $ localized_data );
115+ }
116+
117+ /**
118+ * RestUrl must be an absolute URL, not a root-relative path like /wp-json/...
119+ * A root-relative URL on a subdomain multisite would resolve to the main site.
120+ */
121+ public function testRestUrlIsAbsolute (): void {
122+ $ localized_data = $ this ->enqueueAndGetLocalizedData ();
123+
124+ // The value following "restUrl" must not be a bare /wp-json path.
125+ $ this ->assertDoesNotMatchRegularExpression ( '/"restUrl"\s*:\s*" \\\\?\/wp-json/ ' , $ localized_data );
126+ // And the scheme must be present.
127+ $ this ->assertMatchesRegularExpression ( '/"restUrl"\s*:\s*"https?/ ' , $ localized_data );
128+ }
129+
130+ /**
131+ * FixesRestUrl is present in the localized data passed to the frontend highlighter script.
132+ */
133+ public function testLocalizedDataIncludesFixesRestUrl (): void {
134+ $ localized_data = $ this ->enqueueAndGetLocalizedData ();
135+
136+ $ this ->assertStringContainsString ( 'fixesRestUrl ' , $ localized_data );
137+ }
138+
139+ /**
140+ * FixesRestUrl uses the edac/v1 namespace and matches rest_url().
141+ */
142+ public function testFixesRestUrlContainsEdacV1Namespace (): void {
143+ $ localized_data = $ this ->enqueueAndGetLocalizedData ();
144+ $ expected = rest_url ( 'edac/v1 ' );
145+
146+ $ this ->assertStringContainsString ( 'edac ' , $ localized_data );
147+ $ this ->assertStringContainsString ( (string ) wp_parse_url ( $ expected , PHP_URL_HOST ), $ localized_data );
148+ }
149+
150+ /**
151+ * FixesRestUrl must be an absolute URL, not a root-relative path.
152+ */
153+ public function testFixesRestUrlIsAbsolute (): void {
154+ $ localized_data = $ this ->enqueueAndGetLocalizedData ();
155+
156+ $ this ->assertDoesNotMatchRegularExpression ( '/"fixesRestUrl"\s*:\s*" \\\\?\/wp-json/ ' , $ localized_data );
157+ $ this ->assertMatchesRegularExpression ( '/"fixesRestUrl"\s*:\s*"https?/ ' , $ localized_data );
158+ }
159+
160+ /**
161+ * RestUrl must follow a custom REST base prefix set via the rest_url_prefix filter.
162+ * Verifies the URL is built with rest_url() rather than a hardcoded /wp-json/ string.
163+ * Pretty permalinks are required for the prefix filter to be applied.
164+ */
165+ public function testRestUrlRespectsCustomRestPrefix (): void {
166+ update_option ( 'permalink_structure ' , '/%postname%/ ' );
167+ flush_rewrite_rules (); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.flush_rewrite_rules_flush_rewrite_rules
168+
169+ $ prefix_callback = static fn () => 'custom-api ' ;
170+ add_filter ( 'rest_url_prefix ' , $ prefix_callback );
171+ $ this ->added_filters ['rest_url_prefix ' ] = $ prefix_callback ;
172+
173+ $ localized_data = $ this ->enqueueAndGetLocalizedData ();
174+
175+ remove_filter ( 'rest_url_prefix ' , $ prefix_callback );
176+ delete_option ( 'permalink_structure ' );
177+
178+ $ this ->assertStringContainsString ( 'custom-api ' , $ localized_data );
179+ $ this ->assertStringNotContainsString ( 'wp-json ' , $ localized_data );
180+ }
181+
182+ /**
183+ * FixesRestUrl must follow a custom REST base prefix set via the rest_url_prefix filter.
184+ */
185+ public function testFixesRestUrlRespectsCustomRestPrefix (): void {
186+ update_option ( 'permalink_structure ' , '/%postname%/ ' );
187+ flush_rewrite_rules (); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.flush_rewrite_rules_flush_rewrite_rules
188+
189+ $ prefix_callback = static fn () => 'custom-api ' ;
190+ add_filter ( 'rest_url_prefix ' , $ prefix_callback );
191+ $ this ->added_filters ['rest_url_prefix ' ] = $ prefix_callback ;
192+
193+ $ localized_data = $ this ->enqueueAndGetLocalizedData ();
194+
195+ remove_filter ( 'rest_url_prefix ' , $ prefix_callback );
196+ delete_option ( 'permalink_structure ' );
197+
198+ $ this ->assertStringContainsString ( 'custom-api ' , $ localized_data );
199+ $ this ->assertStringNotContainsString ( 'wp-json ' , $ localized_data );
200+ }
201+
76202 /**
77203 * Ensure the highlighter uses the filtered post ID when determining scannable post types.
78204 */
0 commit comments