PluginProbe
Custom 404 Pro / trunk
Custom 404 Pro vtrunk
3.15.2 3.15.4 3.15.5 3.15.6 3.16.0 3.15.1 3.15.0 3.14.1 3.14.0 3.13.0 trunk 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.1.5 1.1.6 1.2.0 1.3.10 1.3.12 1.3.5 All 100 releases
custom-404-pro / tests / AdminClassTest.php

AdminClassTest.php in Custom 404 Pro trunk, at tests/AdminClassTest.php

297 lines 10.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Unit tests for the AdminClass class.
4 *
5 * @package Custom_404_Pro
6 */
7
8 use PHPUnit\Framework\TestCase;
9
10 /**
11 * AdminClass test case.
12 */
13 class AdminClassTest extends TestCase {
14
15 /**
16 * Asserts that helpers is an explicitly declared property.
17 */
18 public function test_helpers_is_declared_property() {
19 $ref = new ReflectionClass( AdminClass::class );
20 $this->assertTrue(
21 $ref->hasProperty( 'helpers' ),
22 'helpers must be explicitly declared to avoid PHP 8.2+ deprecation'
23 );
24 }
25
26 /**
27 * Asserts that helpers is an instance of Helpers after construction.
28 */
29 public function test_helpers_is_instance_of_helpers_after_construction() {
30 $admin = new AdminClass();
31 $ref = new ReflectionClass( $admin );
32 $prop = $ref->getProperty( 'helpers' );
33 $prop->setAccessible( true );
34 $this->assertInstanceOf( Helpers::class, $prop->getValue( $admin ) );
35 }
36
37 /**
38 * Resets multilingual stubs, filter registry, redirect captures, and transient store before each test.
39 */
40 protected function setUp(): void {
41 parent::setUp();
42 $GLOBALS['_test_filters'] = array();
43 $GLOBALS['_test_transients'] = array();
44 $GLOBALS['_test_options'] = array();
45 $GLOBALS['_test_is_404'] = false;
46 $GLOBALS['_test_permalinks'] = array();
47 $GLOBALS['_test_redirect_url'] = null;
48 $GLOBALS['_test_redirect_status'] = null;
49 $GLOBALS['_pll_get_post_return'] = false;
50 unset( $GLOBALS['_pll_current_language'] );
51 unset( $GLOBALS['_pll_default_language'] );
52 }
53
54 /**
55 * Asserts that the original page ID is returned when Polylang finds no translation.
56 */
57 public function test_resolve_multilingual_page_id_returns_original_when_polylang_finds_no_translation() {
58 $GLOBALS['_pll_get_post_return'] = false; // pll_get_post returns false → no translation.
59 $admin = new AdminClass();
60 $this->assertSame( 42, $admin->resolve_multilingual_page_id( 42 ) );
61 }
62
63 /**
64 * Asserts that the translated page ID is returned when Polylang finds a translation.
65 */
66 public function test_resolve_multilingual_page_id_returns_translated_id_from_polylang() {
67 $GLOBALS['_pll_get_post_return'] = 99;
68 $GLOBALS['_pll_current_language'] = 'fr';
69 $admin = new AdminClass();
70 $this->assertSame( 99, $admin->resolve_multilingual_page_id( 42 ) );
71 }
72
73 /**
74 * Asserts that the translated page ID is returned when a WPML filter is registered.
75 */
76 public function test_resolve_multilingual_page_id_returns_translated_id_from_wpml_filter() {
77 add_filter(
78 'wpml_object_id',
79 function ( $page_id ) {
80 return 77; // Simulate WPML returning the translated page ID.
81 }
82 );
83 $admin = new AdminClass();
84 $this->assertSame( 77, $admin->resolve_multilingual_page_id( 42 ) );
85 }
86
87 /**
88 * Asserts that the original page ID is returned when no WPML filter is registered.
89 */
90 public function test_resolve_multilingual_page_id_returns_original_when_no_wpml_filter_registered() {
91 $admin = new AdminClass();
92 $this->assertSame( 42, $admin->resolve_multilingual_page_id( 42 ) );
93 }
94
95 // ------------------------------------------------------------------
96 // normalize_page_id_to_default_language
97 // ------------------------------------------------------------------
98
99 /**
100 * Calls the private normalize_page_id_to_default_language method via reflection.
101 *
102 * @param AdminClass $admin Instance to invoke on.
103 * @param int $page_id Page ID to normalize.
104 * @return int
105 */
106 private function normalize( AdminClass $admin, int $page_id ): int {
107 $ref = new ReflectionClass( $admin );
108 $method = $ref->getMethod( 'normalize_page_id_to_default_language' );
109 $method->setAccessible( true );
110 return $method->invoke( $admin, $page_id );
111 }
112
113 /**
114 * Asserts that the original ID is returned when no multilingual plugin is active.
115 */
116 public function test_normalize_returns_original_id_when_no_multilingual_plugin_active() {
117 $admin = new AdminClass();
118 $this->assertSame( 42, $this->normalize( $admin, 42 ) );
119 }
120
121 /**
122 * Asserts that the WPML default-language ID is returned when a WPML filter is registered.
123 */
124 public function test_normalize_returns_default_language_id_via_wpml() {
125 // Register wpml_default_language filter.
126 add_filter( 'wpml_default_language', function () { return 'en'; } );
127 // Register wpml_object_id filter that returns the default-language page ID.
128 add_filter(
129 'wpml_object_id',
130 function ( $page_id ) {
131 return 5; // Simulate WPML returning the English (default) page for any input.
132 }
133 );
134 $admin = new AdminClass();
135 $this->assertSame( 5, $this->normalize( $admin, 20 ) );
136 }
137
138 /**
139 * Asserts that the Polylang default-language ID is returned when pll_get_post finds a translation.
140 */
141 public function test_normalize_returns_default_language_id_via_polylang() {
142 $GLOBALS['_pll_default_language'] = 'en';
143 $GLOBALS['_pll_get_post_return'] = 10; // pll_get_post returns the default-language page.
144 $admin = new AdminClass();
145 $this->assertSame( 10, $this->normalize( $admin, 20 ) );
146 }
147
148 /**
149 * Asserts that the original ID is returned when Polylang finds no default-language translation.
150 */
151 public function test_normalize_returns_original_id_when_polylang_finds_no_default_translation() {
152 $GLOBALS['_pll_default_language'] = 'en';
153 $GLOBALS['_pll_get_post_return'] = false; // pll_get_post returns false → no translation.
154 $admin = new AdminClass();
155 $this->assertSame( 20, $this->normalize( $admin, 20 ) );
156 }
157
158 // ------------------------------------------------------------------
159 // custom_404_pro_redirect — page mode
160 // ------------------------------------------------------------------
161
162 /**
163 * Helper: seed settings into the test options store.
164 *
165 * @param array $settings Settings to merge into defaults.
166 */
167 private function seed_settings( array $settings ): void {
168 $defaults = array(
169 'mode' => '',
170 'mode_page' => '0',
171 'mode_url' => '',
172 'redirect_error_code' => 302,
173 'logging_enabled' => false,
174 'send_email' => false,
175 'log_ip' => true,
176 'email_cooldown' => HOUR_IN_SECONDS,
177 'max_log_count' => 0,
178 'max_log_age' => 0,
179 );
180 update_option( Helpers::OPTION_KEY, array_merge( $defaults, $settings ) );
181 }
182
183 /**
184 * Asserts that page mode redirects to the URL returned by get_permalink(), not any other value.
185 */
186 public function test_redirect_page_mode_uses_get_permalink() {
187 $GLOBALS['_test_is_404'] = true;
188 $GLOBALS['_test_permalinks'][42] = 'https://example.com/custom-404/';
189 $this->seed_settings( array( 'mode' => 'page', 'mode_page' => '42' ) );
190
191 $admin = new AdminClass();
192 $admin->custom_404_pro_redirect();
193
194 $this->assertSame(
195 'https://example.com/custom-404/',
196 $GLOBALS['_test_redirect_url'],
197 'Redirect URL must come from get_permalink(), not post->guid or any other property.'
198 );
199 }
200
201 /**
202 * Asserts that no redirect fires in page mode when get_permalink() returns false
203 * (e.g. the configured page ID no longer exists).
204 */
205 public function test_redirect_page_mode_does_not_redirect_when_permalink_is_false() {
206 $GLOBALS['_test_is_404'] = true;
207 $GLOBALS['_test_permalinks'][99] = false; // page deleted / unpublished
208 $this->seed_settings( array( 'mode' => 'page', 'mode_page' => '99' ) );
209
210 $admin = new AdminClass();
211 $admin->custom_404_pro_redirect();
212
213 $this->assertNull(
214 $GLOBALS['_test_redirect_url'],
215 'No redirect should fire when get_permalink() returns false.'
216 );
217 }
218
219 /**
220 * Asserts that no redirect fires when the current request is not a 404.
221 */
222 public function test_redirect_page_mode_does_not_fire_when_not_404() {
223 $GLOBALS['_test_is_404'] = false;
224 $GLOBALS['_test_permalinks'][42] = 'https://example.com/custom-404/';
225 $this->seed_settings( array( 'mode' => 'page', 'mode_page' => '42' ) );
226
227 $admin = new AdminClass();
228 $admin->custom_404_pro_redirect();
229
230 $this->assertNull(
231 $GLOBALS['_test_redirect_url'],
232 'No redirect should fire for non-404 requests.'
233 );
234 }
235
236 // ------------------------------------------------------------------
237 // is_email_on_cooldown
238 // ------------------------------------------------------------------
239
240 /**
241 * Asserts that is_email_on_cooldown() returns false when no transient has been set.
242 */
243 public function test_is_email_on_cooldown_returns_false_when_no_transient_set() {
244 $admin = new AdminClass();
245 $this->assertFalse( $admin->is_email_on_cooldown() );
246 }
247
248 /**
249 * Asserts that is_email_on_cooldown() returns true when the cooldown transient is set.
250 */
251 public function test_is_email_on_cooldown_returns_true_when_transient_is_set() {
252 set_transient( 'custom_404_pro_email_cooldown', true, HOUR_IN_SECONDS );
253 $admin = new AdminClass();
254 $this->assertTrue( $admin->is_email_on_cooldown() );
255 }
256
257 /**
258 * Asserts that is_email_on_cooldown() returns false after the cooldown transient is deleted.
259 */
260 public function test_is_email_on_cooldown_returns_false_after_transient_deleted() {
261 set_transient( 'custom_404_pro_email_cooldown', true, HOUR_IN_SECONDS );
262 delete_transient( 'custom_404_pro_email_cooldown' );
263 $admin = new AdminClass();
264 $this->assertFalse( $admin->is_email_on_cooldown() );
265 }
266
267 // ------------------------------------------------------------------
268 // Asset cache busting
269 // ------------------------------------------------------------------
270
271 /**
272 * Admin assets must be versioned with the plugin version so that each
273 * release invalidates the browser cache. This was pinned to a hardcoded
274 * '3.2.0' for many releases, leaving users on stale CSS and JS.
275 */
276 public function test_asset_version_tracks_the_plugin_version() {
277 $this->assertSame( CUSTOM_404_PRO_VERSION, AdminClass::asset_version() );
278 }
279
280 /**
281 * The asset version must no longer be the stale hardcoded value.
282 */
283 public function test_asset_version_is_not_the_stale_hardcoded_value() {
284 $this->assertNotSame( '3.2.0', AdminClass::asset_version() );
285 }
286
287 /**
288 * asset_version() must be callable statically from the enqueue callbacks.
289 */
290 public function test_asset_version_is_public_static() {
291 $ref = new ReflectionClass( AdminClass::class );
292 $method = $ref->getMethod( 'asset_version' );
293 $this->assertTrue( $method->isPublic(), 'asset_version must be public' );
294 $this->assertTrue( $method->isStatic(), 'asset_version must be static' );
295 }
296 }
297