From 842ed35bbf1f4af100ad87b6adf8333a085b823f Mon Sep 17 00:00:00 2001 From: Giuseppe Mazzapica Date: Mon, 12 Dec 2022 19:21:42 +0100 Subject: [PATCH 1/8] Introduce Functions\stubWpUrlFunctions() helper It makes use of a new UrlsHelper class. Tests and documentation added. See #125 --- .../functions-testing-tools/function-stubs.md | 38 +++++- inc/api.php | 34 +++++ src/Expectation/UrlsHelper.php | 125 ++++++++++++++++++ tests/cases/unit/Api/FunctionsTest.php | 60 +++++++++ 4 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 src/Expectation/UrlsHelper.php diff --git a/docs/functions-testing-tools/function-stubs.md b/docs/functions-testing-tools/function-stubs.md index b6163b8..031c301 100644 --- a/docs/functions-testing-tools/function-stubs.md +++ b/docs/functions-testing-tools/function-stubs.md @@ -88,7 +88,7 @@ Functions\stubs( ); ``` -### Pre-defined stubs for escaping functions +### Pre-defined stubs for WP escaping functions To stub WordPress escaping functions is a very common usage for `Functions\stubs`. @@ -111,7 +111,7 @@ By calling `Functions\stubEscapeFunctions()`, for _all_ of the functions listed It will _not_ be the exact same escape mechanism that WordPress would apply, but "similar enough" for unit tests purpose and could still be helpful to discover some bugs. -### Pre-defined stubs for translation functions +### Pre-defined stubs for WP translation functions Another common usage for `Functions\stubs`, since its introduction, has been to stub translation functions. @@ -141,6 +141,40 @@ Only for functions that both translate and escape \(`esc_html__()`, `esc_html_x( Please note how `Functions\stubTranslationFunctions()` creates stubs for functions that _echo_ translated text, something not easily doable with `Functions\stubs()` alone. +### Pre-defined stubs for WP URL functions + +One very common and repetitive task testing WordPress code without WordPress loaded is to stub +WordPress URLs, a task made easy by the function: + +**`Functions\stubWpUrlFunctions()`** + +When called, it will create a stub for _all_ the following functions: + +* `home_url()` +* `get_home_url()` +* `site_url()` +* `get_site_url()` +* `admin_url()` +* `get_admin_url()` +* `content_url()` +* `rest_url()` +* `get_rest_url()` +* `includes_url()` +* `network_home_url()` +* `network_site_url()` +* `network_admin_url()` +* `user_admin_url()` +* `wp_login_url()` + +The function accepts as first argument the domain to use for subbing the URLs, default to `example.org`. + +E.g., after `Functions\stubWpUrlFunctions()`, the `home_url()` function returns `https://example.org`, +but after `Functions\stubWpUrlFunctions('acme.com')`, the `home_url()` function returns `https://acme.org`. + +The function also accepts a second parameter to force the usage of HTTPs protocol. By default, that +second parameter is `null` which makes the stub using HTTPs, _unless_ `is_ssl()` is defined, and +it returns `false`. + ### Gotcha for `Functions\stubs` #### Functions that returns null diff --git a/inc/api.php b/inc/api.php index e5c41c7..a0f46bf 100644 --- a/inc/api.php +++ b/inc/api.php @@ -45,6 +45,7 @@ function tearDown() use Brain\Monkey\Container; use Brain\Monkey\Expectation\EscapeHelper; use Brain\Monkey\Expectation\FunctionStubFactory; + use Brain\Monkey\Expectation\UrlsHelper; use Brain\Monkey\Name\FunctionName; /** @@ -182,6 +183,39 @@ function stubEscapeFunctions() ] ); } + + /** + * Stub URL-related functions with default behavior. + */ + function stubWpUrlFunctions($domain = 'example.org', $use_https = null) + { + $helper = new UrlsHelper($domain, $use_https); + + stubs([ + 'home_url' => $helper->stubUrlCallback(), + 'get_home_url' => $helper->stubUrlForSiteCallback(), + 'site_url' => $helper->stubUrlCallback(), + 'get_site_url' => $helper->stubUrlForSiteCallback(), + 'admin_url' => $helper->stubUrlCallback('wp-admin', 'admin'), + 'get_admin_url' => $helper->stubUrlForSiteCallback('wp-admin', 'admin'), + 'content_url' => $helper->stubUrlCallback('wp-content', null, false), + 'rest_url' => $helper->stubUrlCallback('wp-json'), + 'get_rest_url' => $helper->stubUrlForSiteCallback('wp-json'), + 'includes_url' => $helper->stubUrlCallback('wp-includes'), + 'network_home_url' => $helper->stubUrlCallback(), + 'network_site_url' => $helper->stubUrlCallback(), + 'network_admin_url' => $helper->stubUrlCallback('wp-admin/network', 'admin'), + 'user_admin_url' => $helper->stubUrlCallback('wp-admin/user', 'admin'), + 'wp_login_url' => static function ($redirect = '', $force_reauth = false) use ($helper) { + $callback = $helper->stubUrlCallback(); + $url = $callback('/wp-login.php', 'login'); + $has_redirect = ($redirect !== '') && is_string($redirect); + $has_redirect and $url .= '?redirect_to=' . urlencode($redirect); + $force_reauth and $url .= ($has_redirect ? '&reauth=1' : '?reauth=1'); + return $url; + }, + ]); + } } namespace Brain\Monkey\Actions { diff --git a/src/Expectation/UrlsHelper.php b/src/Expectation/UrlsHelper.php new file mode 100644 index 0000000..c424e23 --- /dev/null +++ b/src/Expectation/UrlsHelper.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Brain\Monkey\Expectation; + +class UrlsHelper +{ + /** + * @var string + */ + private $domain; + + /** + * @var bool|null + */ + private $use_https; + + /** + * @param $domain + * @param $use_https + */ + public function __construct($domain = 'example.org', $use_https = null) + { + $this->domain = (is_string($domain) && $domain) ? $domain : 'example.org'; + $this->use_https = ($use_https === null) + ? null + : (bool)filter_var($use_https, FILTER_VALIDATE_BOOLEAN); + } + + /** + * @param $base_path + * @param $for_admin + * @return \Closure + */ + public function stubUrlForSiteCallback($base_path = '', $def_schema = null) + { + return function ($site_id, $path = '', $schema = null) use ($base_path, $def_schema) { + ($def_schema && $schema === null) and $schema = $def_schema; + return $this->build_url( + $this->build_relative_path($base_path, $path), + $this->determineSchema($schema) + ); + }; + } + + /** + * @param $base_path + * @param $for_admin + * @param $use_schema_arg + * @return \Closure + */ + public function stubUrlCallback($base_path = '', $def_schema = null, $use_schema_arg = true) + { + return function ($path = '', $schema = null) use ($base_path, $def_schema, $use_schema_arg) { + ($def_schema && $schema === null) and $schema = $def_schema; + return $this->build_url( + $this->build_relative_path($base_path, $path), + $this->determineSchema($use_schema_arg ? $schema : null) + ); + }; + } + + /** + * @param $relative + * @param $schema + * @return mixed|string + */ + private function build_url($relative, $schema) + { + return ($schema === null) ? ($relative ?: '/') : $schema . $this->domain . $relative; + } + + /** + * @param $base_path + * @param $path + * @return string + */ + private function build_relative_path($base_path, $path) + { + $path = (($path !== '') && is_string($path)) + ? '/' . ltrim($path, '/') + : ''; + $base_path = (($base_path !== '') && is_string($base_path)) + ? '/' . trim($base_path, '/') + : ''; + + return $base_path . $path; + } + + /** + * @param $schema_argument + * @return string|null + */ + private function determineSchema($schema_argument = null) + { + if ($schema_argument === 'relative') { + return null; + } + + $use_https = $this->use_https; + $is_ssl = function_exists('is_ssl') ? is_ssl() : true; + if ($use_https === null && !in_array($schema_argument, ['http', 'https'], true)) { + $use_https = $is_ssl; + if ( + !$use_https + && in_array($schema_argument, ['admin', 'login', 'login_post', 'rpc']) + && function_exists('force_ssl_admin') + ) { + $use_https = force_ssl_admin(); + } + } + if ($schema_argument === 'http') { + $use_https = false; + } + + return $use_https ? 'https://' : 'http://'; + } +} diff --git a/tests/cases/unit/Api/FunctionsTest.php b/tests/cases/unit/Api/FunctionsTest.php index a7e0097..210c283 100644 --- a/tests/cases/unit/Api/FunctionsTest.php +++ b/tests/cases/unit/Api/FunctionsTest.php @@ -431,4 +431,64 @@ public function dataStubsEscapeXml() ], ]; } + + public function testStubWpUrlFunctionsWithDefaults() + { + Functions\stubWpUrlFunctions(); + + static::assertSame('https://example.org', home_url()); + static::assertSame('https://example.org/', home_url('/')); + static::assertSame('https://example.org/', get_home_url(1, '/')); + static::assertSame('https://example.org/', site_url('/')); + static::assertSame('https://example.org/', get_site_url(2, '/')); + static::assertSame('https://example.org/wp-admin/post-new.php', admin_url('/post-new.php')); + static::assertSame('https://example.org/wp-admin/post-new.php', admin_url('post-new.php')); + static::assertSame('/wp-admin/post-new.php', admin_url('/post-new.php', 'relative')); + static::assertSame('https://example.org/wp-admin/', get_admin_url(1, '/')); + static::assertSame('https://example.org/wp-content/plugins/foo/img.jpg', content_url('/plugins/foo/img.jpg')); + static::assertSame('https://example.org/wp-json', rest_url()); + static::assertSame('https://example.org/wp-json/wp/', get_rest_url(1, '/wp/')); + static::assertSame('https://example.org/wp-includes', includes_url()); + static::assertSame('https://example.org', network_home_url()); + static::assertSame('https://example.org', network_site_url()); + static::assertSame('https://example.org/wp-admin/network', network_admin_url()); + static::assertSame('https://example.org/wp-admin/user/', user_admin_url('/')); + } + + public function testStubWpUrlFunctionsWithSettings() + { + Functions\stubWpUrlFunctions('wikipedia.org', false); + + static::assertSame('http://wikipedia.org', home_url()); + static::assertSame('http://wikipedia.org/wp-admin/post-new.php', admin_url('/post-new.php')); + } + + public function testStubWpUrlViaIsSslFunctions() + { + Functions\when('is_ssl')->justReturn(false); + Functions\when('force_ssl_admin')->justReturn(true); + Functions\stubWpUrlFunctions(); + + static::assertSame('http://example.org', home_url()); + static::assertSame('https://example.org/wp-admin/', admin_url('/')); + static::assertSame('https://example.org/wp-admin/network', network_admin_url()); + static::assertSame('https://example.org/wp-login.php', wp_login_url()); + } + + public function testStubWpLoginUrl() + { + Functions\stubWpUrlFunctions(); + Functions\when('is_ssl')->justReturn(true); + + static::assertSame('https://example.org/wp-login.php', wp_login_url()); + static::assertSame('https://example.org/wp-login.php?reauth=1', wp_login_url('', true)); + static::assertSame( + 'https://example.org/wp-login.php?redirect_to=https%3A%2F%2Fexample.org', + wp_login_url('https://example.org') + ); + static::assertSame( + 'https://example.org/wp-login.php?redirect_to=https%3A%2F%2Fexample.org&reauth=1', + wp_login_url('https://example.org', true) + ); + } } From 0e63888429c3270dc9061a267fe74826803096a9 Mon Sep 17 00:00:00 2001 From: Giuseppe Mazzapica Date: Wed, 19 Apr 2023 09:33:15 +0200 Subject: [PATCH 2/8] Update docs/functions-testing-tools/function-stubs.md Co-authored-by: Juliette <663378+jrfnl@users.noreply.github.com> --- docs/functions-testing-tools/function-stubs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/functions-testing-tools/function-stubs.md b/docs/functions-testing-tools/function-stubs.md index 031c301..bae1104 100644 --- a/docs/functions-testing-tools/function-stubs.md +++ b/docs/functions-testing-tools/function-stubs.md @@ -168,8 +168,8 @@ When called, it will create a stub for _all_ the following functions: The function accepts as first argument the domain to use for subbing the URLs, default to `example.org`. -E.g., after `Functions\stubWpUrlFunctions()`, the `home_url()` function returns `https://example.org`, -but after `Functions\stubWpUrlFunctions('acme.com')`, the `home_url()` function returns `https://acme.org`. +E.g., calling `Functions\stubWpUrlFunctions()`, the `home_url()` function returns `https://example.org`, +but calling `Functions\stubWpUrlFunctions('acme.com')`, the `home_url()` function returns `https://acme.org`. The function also accepts a second parameter to force the usage of HTTPs protocol. By default, that second parameter is `null` which makes the stub using HTTPs, _unless_ `is_ssl()` is defined, and From fe08d2ad9532e05ebedbcdb4f428e1dd1f40c41c Mon Sep 17 00:00:00 2001 From: Giuseppe Mazzapica Date: Wed, 19 Apr 2023 09:33:39 +0200 Subject: [PATCH 3/8] Update docs/functions-testing-tools/function-stubs.md Co-authored-by: Juliette <663378+jrfnl@users.noreply.github.com> --- docs/functions-testing-tools/function-stubs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/functions-testing-tools/function-stubs.md b/docs/functions-testing-tools/function-stubs.md index bae1104..7f01e17 100644 --- a/docs/functions-testing-tools/function-stubs.md +++ b/docs/functions-testing-tools/function-stubs.md @@ -171,8 +171,8 @@ The function accepts as first argument the domain to use for subbing the URLs, d E.g., calling `Functions\stubWpUrlFunctions()`, the `home_url()` function returns `https://example.org`, but calling `Functions\stubWpUrlFunctions('acme.com')`, the `home_url()` function returns `https://acme.org`. -The function also accepts a second parameter to force the usage of HTTPs protocol. By default, that -second parameter is `null` which makes the stub using HTTPs, _unless_ `is_ssl()` is defined, and +The function also accepts a second parameter to force the usage of the HTTPS protocol. By default, that +second parameter is `null` which makes the stub use HTTPS, _unless_ the `is_ssl()` function is defined (stubbed), and it returns `false`. ### Gotcha for `Functions\stubs` From d2f3281935a1b9e409f25539ab196c3640c82a0b Mon Sep 17 00:00:00 2001 From: Giuseppe Mazzapica Date: Wed, 19 Apr 2023 09:34:20 +0200 Subject: [PATCH 4/8] Update inc/api.php Co-authored-by: Juliette <663378+jrfnl@users.noreply.github.com> --- inc/api.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/api.php b/inc/api.php index a0f46bf..f00b263 100644 --- a/inc/api.php +++ b/inc/api.php @@ -210,8 +210,8 @@ function stubWpUrlFunctions($domain = 'example.org', $use_https = null) $callback = $helper->stubUrlCallback(); $url = $callback('/wp-login.php', 'login'); $has_redirect = ($redirect !== '') && is_string($redirect); - $has_redirect and $url .= '?redirect_to=' . urlencode($redirect); - $force_reauth and $url .= ($has_redirect ? '&reauth=1' : '?reauth=1'); + $url .= $has_redirect ? '?redirect_to=' . urlencode($redirect) : ''; + $url .= $force_reauth ? ($has_redirect ? '&reauth=1' : '?reauth=1') : ''; return $url; }, ]); From 9a5692063f14084a0859077faf1cc398f302b774 Mon Sep 17 00:00:00 2001 From: Giuseppe Mazzapica Date: Wed, 19 Apr 2023 09:51:01 +0200 Subject: [PATCH 5/8] Add UrlsHelper::DEFAULT_DOMAIN constant --- src/Expectation/UrlsHelper.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Expectation/UrlsHelper.php b/src/Expectation/UrlsHelper.php index c424e23..52c83d4 100644 --- a/src/Expectation/UrlsHelper.php +++ b/src/Expectation/UrlsHelper.php @@ -12,6 +12,8 @@ class UrlsHelper { + const DEFAULT_DOMAIN = 'example.org'; + /** * @var string */ @@ -26,9 +28,9 @@ class UrlsHelper * @param $domain * @param $use_https */ - public function __construct($domain = 'example.org', $use_https = null) + public function __construct($domain = null, $use_https = null) { - $this->domain = (is_string($domain) && $domain) ? $domain : 'example.org'; + $this->domain = (is_string($domain) && $domain !== '') ? $domain : self::DEFAULT_DOMAIN; $this->use_https = ($use_https === null) ? null : (bool)filter_var($use_https, FILTER_VALIDATE_BOOLEAN); From 1921ed1d84618d808d4016bae73e7d165d21df35 Mon Sep 17 00:00:00 2001 From: Giuseppe Mazzapica Date: Wed, 19 Apr 2023 09:51:17 +0200 Subject: [PATCH 6/8] Fix wromng doc block in UrlsHelper --- src/Expectation/UrlsHelper.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Expectation/UrlsHelper.php b/src/Expectation/UrlsHelper.php index 52c83d4..fbb608f 100644 --- a/src/Expectation/UrlsHelper.php +++ b/src/Expectation/UrlsHelper.php @@ -38,7 +38,7 @@ public function __construct($domain = null, $use_https = null) /** * @param $base_path - * @param $for_admin + * @param $def_schema * @return \Closure */ public function stubUrlForSiteCallback($base_path = '', $def_schema = null) @@ -54,7 +54,7 @@ public function stubUrlForSiteCallback($base_path = '', $def_schema = null) /** * @param $base_path - * @param $for_admin + * @param $def_schema * @param $use_schema_arg * @return \Closure */ From 1991a83e0135c6bfeb2b090cdafcd7bd171b2faf Mon Sep 17 00:00:00 2001 From: Giuseppe Mazzapica Date: Wed, 19 Apr 2023 11:02:59 +0200 Subject: [PATCH 7/8] Better types handling in UrlsHelper --- src/Expectation/UrlsHelper.php | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Expectation/UrlsHelper.php b/src/Expectation/UrlsHelper.php index fbb608f..badbacf 100644 --- a/src/Expectation/UrlsHelper.php +++ b/src/Expectation/UrlsHelper.php @@ -25,8 +25,8 @@ class UrlsHelper private $use_https; /** - * @param $domain - * @param $use_https + * @param mixed $domain + * @param mixed $use_https */ public function __construct($domain = null, $use_https = null) { @@ -37,14 +37,16 @@ public function __construct($domain = null, $use_https = null) } /** - * @param $base_path - * @param $def_schema + * @param mixed $base_path + * @param mixed $def_schema * @return \Closure */ public function stubUrlForSiteCallback($base_path = '', $def_schema = null) { return function ($site_id, $path = '', $schema = null) use ($base_path, $def_schema) { - ($def_schema && $schema === null) and $schema = $def_schema; + if (is_string($def_schema) && ($def_schema !== '') && ($schema === null)) { + $schema = $def_schema; + } return $this->build_url( $this->build_relative_path($base_path, $path), $this->determineSchema($schema) @@ -53,9 +55,9 @@ public function stubUrlForSiteCallback($base_path = '', $def_schema = null) } /** - * @param $base_path - * @param $def_schema - * @param $use_schema_arg + * @param mixed $base_path + * @param mixed $def_schema + * @param mixed $use_schema_arg * @return \Closure */ public function stubUrlCallback($base_path = '', $def_schema = null, $use_schema_arg = true) @@ -70,18 +72,20 @@ public function stubUrlCallback($base_path = '', $def_schema = null, $use_schema } /** - * @param $relative - * @param $schema - * @return mixed|string + * @param string $relative + * @param string|null $schema + * @return string */ private function build_url($relative, $schema) { - return ($schema === null) ? ($relative ?: '/') : $schema . $this->domain . $relative; + return ($schema === null) + ? (($relative === '') ? '/' : $relative) + : $schema . $this->domain . $relative; } /** - * @param $base_path - * @param $path + * @param mixed $base_path + * @param mixed $path * @return string */ private function build_relative_path($base_path, $path) @@ -97,7 +101,7 @@ private function build_relative_path($base_path, $path) } /** - * @param $schema_argument + * @param mixed $schema_argument * @return string|null */ private function determineSchema($schema_argument = null) From dfffa277b36f479cc92dfbf39732fcdaff24ad5b Mon Sep 17 00:00:00 2001 From: Giuseppe Mazzapica Date: Wed, 19 Apr 2023 11:05:31 +0200 Subject: [PATCH 8/8] Use root namespace for core functions in UrlsHelper Call \is_ssl() and \force_ssl_admin explicitly if stubbed. We expect those to be stubbed in the root namespace. --- src/Expectation/UrlsHelper.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Expectation/UrlsHelper.php b/src/Expectation/UrlsHelper.php index badbacf..20b4f90 100644 --- a/src/Expectation/UrlsHelper.php +++ b/src/Expectation/UrlsHelper.php @@ -111,7 +111,7 @@ private function determineSchema($schema_argument = null) } $use_https = $this->use_https; - $is_ssl = function_exists('is_ssl') ? is_ssl() : true; + $is_ssl = function_exists('is_ssl') ? \is_ssl() : true; if ($use_https === null && !in_array($schema_argument, ['http', 'https'], true)) { $use_https = $is_ssl; if ( @@ -119,7 +119,7 @@ private function determineSchema($schema_argument = null) && in_array($schema_argument, ['admin', 'login', 'login_post', 'rpc']) && function_exists('force_ssl_admin') ) { - $use_https = force_ssl_admin(); + $use_https = \force_ssl_admin(); } } if ($schema_argument === 'http') {