diff --git a/composer.json b/composer.json index 03a8d413c..e98e44b14 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "devstone/ultimate-multisite", "homepage": "https://UltimateMultisite.com", "description": "The Multisite Website as a Service (WaaS) plugin.", - "version": "2.4.11-beta.4", + "version": "2.4.11-beta.3", "authors": [ { "name": "Arindo Duque", diff --git a/inc/class-wp-ultimo.php b/inc/class-wp-ultimo.php index 4544f0354..7800e497c 100644 --- a/inc/class-wp-ultimo.php +++ b/inc/class-wp-ultimo.php @@ -31,7 +31,7 @@ final class WP_Ultimo { * @since 2.1.0 * @var string */ - const VERSION = '2.4.11-beta.4'; + const VERSION = '2.4.11-beta.3'; /** * Core log handle for Ultimate Multisite. diff --git a/inc/compat/class-multiple-accounts-compat.php b/inc/compat/class-multiple-accounts-compat.php index 781ba4ca7..b6c828212 100644 --- a/inc/compat/class-multiple-accounts-compat.php +++ b/inc/compat/class-multiple-accounts-compat.php @@ -423,6 +423,12 @@ public function fix_login($user, $username, $password) { return $user; } + // On the main site, we don't need to scope email lookups + // to a specific sub-site. Let WordPress handle it normally. + if (is_main_site()) { + return $user; + } + // Sets the right user to be returned; $user = $this->get_right_user($username, $password); diff --git a/lang/ultimate-multisite.pot b/lang/ultimate-multisite.pot index cc066319d..c863a80a9 100644 --- a/lang/ultimate-multisite.pot +++ b/lang/ultimate-multisite.pot @@ -2,14 +2,14 @@ # This file is distributed under the GPLv2 or later. msgid "" msgstr "" -"Project-Id-Version: Ultimate Multisite – WordPress Multisite SaaS & WaaS Platform 2.4.10\n" +"Project-Id-Version: Ultimate Multisite – WordPress Multisite SaaS & WaaS Platform 2.4.11-beta.3\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/ultimate-multisite\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2026-02-05T19:55:33+00:00\n" +"POT-Creation-Date: 2026-02-08T01:11:53+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" "X-Domain: ultimate-multisite\n" @@ -82,33 +82,33 @@ msgid "You are trying to download an add-on from an insecure URL" msgstr "" #: inc/admin-pages/class-addons-admin-page.php:270 -#: inc/admin-pages/class-addons-admin-page.php:450 +#: inc/admin-pages/class-addons-admin-page.php:452 msgid "All Add-ons" msgstr "" #. translators: %s error message. -#: inc/admin-pages/class-addons-admin-page.php:314 +#: inc/admin-pages/class-addons-admin-page.php:316 #, php-format msgid "Failed to fetch addons from API: %s" msgstr "" #. translators: %s error message. -#: inc/admin-pages/class-addons-admin-page.php:314 +#: inc/admin-pages/class-addons-admin-page.php:316 msgid "no addons returned" msgstr "" -#: inc/admin-pages/class-addons-admin-page.php:352 -#: inc/admin-pages/class-addons-admin-page.php:363 +#: inc/admin-pages/class-addons-admin-page.php:354 +#: inc/admin-pages/class-addons-admin-page.php:365 #: views/base/settings.php:188 msgid "Add-ons" msgstr "" -#: inc/admin-pages/class-addons-admin-page.php:454 +#: inc/admin-pages/class-addons-admin-page.php:456 #: inc/installers/class-default-content-installer.php:284 msgid "Premium" msgstr "" -#: inc/admin-pages/class-addons-admin-page.php:458 +#: inc/admin-pages/class-addons-admin-page.php:460 #: inc/admin-pages/class-product-edit-admin-page.php:300 #: inc/installers/class-default-content-installer.php:264 #: inc/list-tables/class-membership-list-table-widget.php:166 @@ -120,37 +120,37 @@ msgstr "" msgid "Free" msgstr "" -#: inc/admin-pages/class-addons-admin-page.php:462 +#: inc/admin-pages/class-addons-admin-page.php:464 msgid "Gateways" msgstr "" -#: inc/admin-pages/class-addons-admin-page.php:466 +#: inc/admin-pages/class-addons-admin-page.php:468 msgid "Growth & Scaling" msgstr "" -#: inc/admin-pages/class-addons-admin-page.php:470 -#: inc/class-settings.php:1542 -#: inc/class-settings.php:1543 +#: inc/admin-pages/class-addons-admin-page.php:472 +#: inc/class-settings.php:1554 +#: inc/class-settings.php:1555 msgid "Integrations" msgstr "" -#: inc/admin-pages/class-addons-admin-page.php:474 +#: inc/admin-pages/class-addons-admin-page.php:476 msgid "Customization" msgstr "" -#: inc/admin-pages/class-addons-admin-page.php:478 +#: inc/admin-pages/class-addons-admin-page.php:480 msgid "Admin Themes" msgstr "" -#: inc/admin-pages/class-addons-admin-page.php:482 +#: inc/admin-pages/class-addons-admin-page.php:484 msgid "Monetization" msgstr "" -#: inc/admin-pages/class-addons-admin-page.php:486 +#: inc/admin-pages/class-addons-admin-page.php:488 msgid "Migrators" msgstr "" -#: inc/admin-pages/class-addons-admin-page.php:490 +#: inc/admin-pages/class-addons-admin-page.php:492 msgid "Marketplace" msgstr "" @@ -772,7 +772,7 @@ msgstr "" #: inc/admin-pages/class-checkout-form-edit-admin-page.php:1245 #: inc/admin-pages/class-discount-code-edit-admin-page.php:252 #: inc/admin-pages/class-email-edit-admin-page.php:294 -#: inc/class-settings.php:1753 +#: inc/class-settings.php:1765 msgid "Advanced Options" msgstr "" @@ -1141,7 +1141,7 @@ msgstr "" #: inc/admin-pages/class-product-edit-admin-page.php:843 #: inc/checkout/signup-fields/class-signup-field-period-selection.php:216 #: inc/checkout/signup-fields/class-signup-field-select.php:179 -#: inc/class-settings.php:1153 +#: inc/class-settings.php:1165 #: inc/list-tables/class-membership-line-item-list-table.php:139 #: inc/list-tables/class-payment-line-item-list-table.php:82 #: views/checkout/templates/order-bump/simple.php:49 @@ -1230,8 +1230,8 @@ msgstr "" #: inc/admin-pages/class-membership-list-admin-page.php:311 #: inc/admin-pages/class-membership-list-admin-page.php:322 #: inc/admin-pages/class-membership-list-admin-page.php:333 -#: inc/class-settings.php:966 -#: inc/class-settings.php:967 +#: inc/class-settings.php:978 +#: inc/class-settings.php:979 #: inc/debug/class-debug.php:195 #: inc/list-tables/class-customer-list-table.php:244 #: inc/list-tables/class-membership-list-table-widget.php:42 @@ -1328,8 +1328,8 @@ msgstr "" #: inc/admin-pages/class-payment-list-admin-page.php:255 #: inc/admin-pages/class-payment-list-admin-page.php:266 #: inc/admin-pages/class-top-admin-nav-menu.php:115 -#: inc/class-settings.php:1373 -#: inc/class-settings.php:1374 +#: inc/class-settings.php:1385 +#: inc/class-settings.php:1386 #: inc/debug/class-debug.php:263 #: inc/list-tables/class-payment-list-table-widget.php:42 #: inc/list-tables/class-payment-list-table.php:42 @@ -1342,8 +1342,8 @@ msgstr "" #: inc/admin-pages/class-site-list-admin-page.php:517 #: inc/admin-pages/class-site-list-admin-page.php:528 #: inc/admin-pages/class-site-list-admin-page.php:539 -#: inc/class-settings.php:1213 -#: inc/class-settings.php:1214 +#: inc/class-settings.php:1225 +#: inc/class-settings.php:1226 #: inc/debug/class-debug.php:212 #: inc/list-tables/class-site-list-table.php:45 #: inc/managers/class-limitation-manager.php:276 @@ -2884,7 +2884,7 @@ msgid "Instructions" msgstr "" #: inc/admin-pages/class-hosting-integration-wizard-admin-page.php:155 -#: inc/integrations/class-integration-registry.php:354 +#: inc/integrations/class-integration-registry.php:355 msgid "Configuration" msgstr "" @@ -4479,8 +4479,8 @@ msgid "This action cannot be undone. Make sure you have a backup of your current msgstr "" #: inc/admin-pages/class-settings-admin-page.php:711 -#: inc/class-settings.php:1624 -#: inc/class-settings.php:1637 +#: inc/class-settings.php:1636 +#: inc/class-settings.php:1649 msgid "Import Settings" msgstr "" @@ -4654,7 +4654,7 @@ msgid "WordPress Cron" msgstr "" #: inc/admin-pages/class-setup-wizard-admin-page.php:683 -#: inc/integrations/class-integration-registry.php:344 +#: inc/integrations/class-integration-registry.php:345 msgid "Activated" msgstr "" @@ -4752,7 +4752,7 @@ msgid "Tell your customers what this site is about." msgstr "" #: inc/admin-pages/class-site-edit-admin-page.php:358 -#: inc/class-settings.php:1223 +#: inc/class-settings.php:1235 msgid "Site Options" msgstr "" @@ -5048,7 +5048,7 @@ msgstr "" #: inc/admin-pages/class-system-info-admin-page.php:479 #: inc/admin-pages/class-system-info-admin-page.php:484 #: inc/admin-pages/class-system-info-admin-page.php:489 -#: inc/class-settings.php:1742 +#: inc/class-settings.php:1754 msgid "Disabled" msgstr "" @@ -8614,7 +8614,7 @@ msgstr "" #: inc/class-orphaned-tables-manager.php:140 #: inc/class-orphaned-users-manager.php:133 -#: inc/class-settings.php:1661 +#: inc/class-settings.php:1673 msgid "Warning:" msgstr "" @@ -8748,19 +8748,23 @@ msgstr "" msgid "special character" msgstr "" +#: inc/class-scripts.php:313 +msgid "An unexpected error occurred. Please try again or contact support if the problem persists." +msgstr "" + #. translators: the day/month/year date format used by Ultimate Multisite. You can changed it to localize this date format to your language. the default value is d/m/Y, which is the format 31/12/2021. -#: inc/class-scripts.php:348 +#: inc/class-scripts.php:349 msgid "d/m/Y" msgstr "" #. translators: %s is a relative future date. -#: inc/class-scripts.php:358 +#: inc/class-scripts.php:359 #, php-format msgid "in %s" msgstr "" #. translators: %s is a relative past date. -#: inc/class-scripts.php:360 +#: inc/class-scripts.php:361 #: inc/functions/date.php:156 #: inc/list-tables/class-base-list-table.php:851 #: views/admin-pages/fields/field-text-display.php:43 @@ -8769,72 +8773,72 @@ msgstr "" msgid "%s ago" msgstr "" -#: inc/class-scripts.php:361 +#: inc/class-scripts.php:362 msgid "a few seconds" msgstr "" #. translators: %s is the number of seconds. -#: inc/class-scripts.php:363 +#: inc/class-scripts.php:364 #, php-format msgid "%d seconds" msgstr "" -#: inc/class-scripts.php:364 +#: inc/class-scripts.php:365 msgid "a minute" msgstr "" #. translators: %s is the number of minutes. -#: inc/class-scripts.php:366 +#: inc/class-scripts.php:367 #, php-format msgid "%d minutes" msgstr "" -#: inc/class-scripts.php:367 +#: inc/class-scripts.php:368 msgid "an hour" msgstr "" #. translators: %s is the number of hours. -#: inc/class-scripts.php:369 +#: inc/class-scripts.php:370 #, php-format msgid "%d hours" msgstr "" -#: inc/class-scripts.php:370 +#: inc/class-scripts.php:371 msgid "a day" msgstr "" #. translators: %s is the number of days. -#: inc/class-scripts.php:372 +#: inc/class-scripts.php:373 #, php-format msgid "%d days" msgstr "" -#: inc/class-scripts.php:373 +#: inc/class-scripts.php:374 msgid "a week" msgstr "" #. translators: %s is the number of weeks. -#: inc/class-scripts.php:375 +#: inc/class-scripts.php:376 #, php-format msgid "%d weeks" msgstr "" -#: inc/class-scripts.php:376 +#: inc/class-scripts.php:377 msgid "a month" msgstr "" #. translators: %s is the number of months. -#: inc/class-scripts.php:378 +#: inc/class-scripts.php:379 #, php-format msgid "%d months" msgstr "" -#: inc/class-scripts.php:379 +#: inc/class-scripts.php:380 msgid "a year" msgstr "" #. translators: %s is the number of years. -#: inc/class-scripts.php:381 +#: inc/class-scripts.php:382 #, php-format msgid "%d years" msgstr "" @@ -8898,7 +8902,7 @@ msgid "Currency Options" msgstr "" #: inc/class-settings.php:636 -#: inc/class-settings.php:1384 +#: inc/class-settings.php:1396 msgid "The following options affect how prices are displayed on the frontend, the backend and in reports." msgstr "" @@ -8961,479 +8965,487 @@ msgstr "" msgid "Allow Ultimate Multisite to collect anonymous usage data and error reports to help us improve the plugin. We collect: PHP version, WordPress version, plugin version, network type (subdomain/subdirectory), aggregate counts (sites, memberships), active gateways, and error logs. We never collect personal data, customer information, or domain names. Learn more." msgstr "" -#: inc/class-settings.php:735 -#: inc/class-settings.php:736 +#: inc/class-settings.php:731 +msgid "Beta Updates" +msgstr "" + +#: inc/class-settings.php:732 +msgid "Opt in to receive pre-release versions of Ultimate Multisite and its add-ons. Beta versions may contain bugs or incomplete features." +msgstr "" + +#: inc/class-settings.php:747 +#: inc/class-settings.php:748 msgid "Login & Registration" msgstr "" -#: inc/class-settings.php:745 +#: inc/class-settings.php:757 msgid "Login and Registration Options" msgstr "" -#: inc/class-settings.php:746 +#: inc/class-settings.php:758 msgid "Options related to registration and login behavior." msgstr "" -#: inc/class-settings.php:755 +#: inc/class-settings.php:767 msgid "Enable Registration" msgstr "" -#: inc/class-settings.php:756 +#: inc/class-settings.php:768 msgid "Turning this toggle off will disable registration in all checkout forms across the network." msgstr "" -#: inc/class-settings.php:766 +#: inc/class-settings.php:778 msgid "Email verification" msgstr "" -#: inc/class-settings.php:767 +#: inc/class-settings.php:779 msgid "Controls if email verification is required during registration. If set, sites will not be created until the customer email verification status is changed to verified." msgstr "" -#: inc/class-settings.php:770 +#: inc/class-settings.php:782 msgid "Never require email verification" msgstr "" -#: inc/class-settings.php:771 +#: inc/class-settings.php:783 msgid "Only for free plans" msgstr "" -#: inc/class-settings.php:772 +#: inc/class-settings.php:784 msgid "Always require email verification" msgstr "" -#: inc/class-settings.php:793 +#: inc/class-settings.php:805 msgid "Default Registration Page" msgstr "" -#: inc/class-settings.php:794 -#: inc/class-settings.php:826 -#: inc/class-settings.php:978 -#: inc/class-settings.php:1235 +#: inc/class-settings.php:806 +#: inc/class-settings.php:838 +#: inc/class-settings.php:990 +#: inc/class-settings.php:1247 msgid "Search pages on the main site..." msgstr "" -#: inc/class-settings.php:795 -#: inc/class-settings.php:979 -#: inc/class-settings.php:1236 +#: inc/class-settings.php:807 +#: inc/class-settings.php:991 +#: inc/class-settings.php:1248 msgid "Only published pages on the main site are available for selection, and you need to make sure they contain a [wu_checkout] shortcode." msgstr "" -#: inc/class-settings.php:813 +#: inc/class-settings.php:825 msgid "Use Custom Login Page" msgstr "" -#: inc/class-settings.php:814 +#: inc/class-settings.php:826 msgid "Turn this toggle on to select a custom page to be used as the login page." msgstr "" -#: inc/class-settings.php:825 +#: inc/class-settings.php:837 msgid "Default Login Page" msgstr "" -#: inc/class-settings.php:827 +#: inc/class-settings.php:839 msgid "Only published pages on the main site are available for selection, and you need to make sure they contain a [wu_login_form] shortcode." msgstr "" -#: inc/class-settings.php:847 +#: inc/class-settings.php:859 msgid "Obfuscate the Original Login URL (wp-login.php)" msgstr "" -#: inc/class-settings.php:848 +#: inc/class-settings.php:860 msgid "If this option is enabled, we will display a 404 error when a user tries to access the original wp-login.php link. This is useful to prevent brute-force attacks." msgstr "" -#: inc/class-settings.php:861 +#: inc/class-settings.php:873 msgid "Use Sub-site logo on Login Page" msgstr "" -#: inc/class-settings.php:862 +#: inc/class-settings.php:874 msgid "Toggle this option to replace the WordPress logo on the sub-site login page with the logo set for that sub-site. If unchecked, the network logo will be used instead." msgstr "" -#: inc/class-settings.php:875 +#: inc/class-settings.php:887 msgid "Force Synchronous Site Publication" msgstr "" -#: inc/class-settings.php:876 +#: inc/class-settings.php:888 msgid "By default, when a new pending site needs to be converted into a real network site, the publishing process happens via Job Queue, asynchronously. Enable this option to force the publication to happen in the same request as the signup. Be careful, as this can cause timeouts depending on the size of the site templates being copied." msgstr "" -#: inc/class-settings.php:886 +#: inc/class-settings.php:898 msgid "Password Strength" msgstr "" -#: inc/class-settings.php:887 +#: inc/class-settings.php:899 msgid "Configure password strength requirements for user registration." msgstr "" -#: inc/class-settings.php:896 +#: inc/class-settings.php:908 msgid "Minimum Password Strength" msgstr "" -#: inc/class-settings.php:897 +#: inc/class-settings.php:909 msgid "Set the minimum password strength required during registration and password reset. \"Super Strong\" requires at least 12 characters, including uppercase, lowercase, numbers, and special characters." msgstr "" -#: inc/class-settings.php:901 +#: inc/class-settings.php:913 msgid "Medium" msgstr "" -#: inc/class-settings.php:902 +#: inc/class-settings.php:914 msgid "Strong" msgstr "" -#: inc/class-settings.php:903 +#: inc/class-settings.php:915 msgid "Super Strong (12+ chars, mixed case, numbers, symbols)" msgstr "" -#: inc/class-settings.php:912 -#: inc/class-settings.php:1679 -#: inc/class-settings.php:1680 +#: inc/class-settings.php:924 +#: inc/class-settings.php:1691 +#: inc/class-settings.php:1692 msgid "Other Options" msgstr "" -#: inc/class-settings.php:913 +#: inc/class-settings.php:925 msgid "Other registration-related options." msgstr "" -#: inc/class-settings.php:922 +#: inc/class-settings.php:934 msgid "Default Role" msgstr "" -#: inc/class-settings.php:923 +#: inc/class-settings.php:935 msgid "Set the role to be applied to the user during the signup process." msgstr "" -#: inc/class-settings.php:934 +#: inc/class-settings.php:946 msgid "Add Users to the Main Site as well?" msgstr "" -#: inc/class-settings.php:935 +#: inc/class-settings.php:947 msgid "Enabling this option will also add the user to the main site of your network." msgstr "" -#: inc/class-settings.php:945 +#: inc/class-settings.php:957 msgid "Add to Main Site with Role..." msgstr "" -#: inc/class-settings.php:946 +#: inc/class-settings.php:958 msgid "Select the role Ultimate Multisite should use when adding the user to the main site of your network. Be careful." msgstr "" -#: inc/class-settings.php:977 +#: inc/class-settings.php:989 msgid "Default Membership Update Page" msgstr "" -#: inc/class-settings.php:997 +#: inc/class-settings.php:1009 msgid "Block Frontend Access" msgstr "" -#: inc/class-settings.php:998 +#: inc/class-settings.php:1010 msgid "Block the frontend access of network sites after a membership is no longer active." msgstr "" -#: inc/class-settings.php:999 +#: inc/class-settings.php:1011 msgid "By default, if a user does not pay and the account goes inactive, only the admin panel will be blocked, but the user's site will still be accessible on the frontend. If enabled, this option will also block frontend access in those cases." msgstr "" -#: inc/class-settings.php:1009 +#: inc/class-settings.php:1021 msgid "Frontend Block Grace Period" msgstr "" -#: inc/class-settings.php:1010 +#: inc/class-settings.php:1022 msgid "Select the number of days Ultimate Multisite should wait after the membership goes inactive before blocking the frontend access. Leave 0 to block immediately after the membership becomes inactive." msgstr "" -#: inc/class-settings.php:1024 +#: inc/class-settings.php:1036 msgid "Frontend Block Page" msgstr "" -#: inc/class-settings.php:1025 +#: inc/class-settings.php:1037 msgid "Select a page on the main site to redirect user if access is blocked" msgstr "" -#: inc/class-settings.php:1045 +#: inc/class-settings.php:1057 msgid "Enable Multiple Memberships per Customer" msgstr "" -#: inc/class-settings.php:1046 +#: inc/class-settings.php:1058 msgid "Enabling this option will allow your users to create more than one membership." msgstr "" -#: inc/class-settings.php:1056 +#: inc/class-settings.php:1068 msgid "Enable Multiple Sites per Membership" msgstr "" -#: inc/class-settings.php:1057 +#: inc/class-settings.php:1069 msgid "Enabling this option will allow your customers to create more than one site. You can limit how many sites your users can create in a per plan basis." msgstr "" -#: inc/class-settings.php:1067 +#: inc/class-settings.php:1079 msgid "Block Sites on Downgrade" msgstr "" -#: inc/class-settings.php:1068 +#: inc/class-settings.php:1080 msgid "Choose how Ultimate Multisite should handle client sites above their plan quota on downgrade." msgstr "" -#: inc/class-settings.php:1072 +#: inc/class-settings.php:1084 msgid "Keep sites as is (do nothing)" msgstr "" -#: inc/class-settings.php:1073 +#: inc/class-settings.php:1085 msgid "Block only frontend access" msgstr "" -#: inc/class-settings.php:1074 +#: inc/class-settings.php:1086 msgid "Block only backend access" msgstr "" -#: inc/class-settings.php:1075 +#: inc/class-settings.php:1087 msgid "Block both frontend and backend access" msgstr "" -#: inc/class-settings.php:1087 +#: inc/class-settings.php:1099 msgid "Move Posts on Downgrade" msgstr "" -#: inc/class-settings.php:1088 +#: inc/class-settings.php:1100 msgid "Select how you want to handle the posts above the quota on downgrade. This will apply to all post types with quotas set." msgstr "" -#: inc/class-settings.php:1092 +#: inc/class-settings.php:1104 msgid "Keep posts as is (do nothing)" msgstr "" -#: inc/class-settings.php:1093 +#: inc/class-settings.php:1105 msgid "Move posts above the new quota to the Trash" msgstr "" -#: inc/class-settings.php:1094 +#: inc/class-settings.php:1106 msgid "Mark posts above the new quota as Drafts" msgstr "" -#: inc/class-settings.php:1104 +#: inc/class-settings.php:1116 msgid "Emulated Post Types" msgstr "" -#: inc/class-settings.php:1105 +#: inc/class-settings.php:1117 msgid "Emulates the registering of a custom post type to be able to create limits for it without having to activate plugins on the main site." msgstr "" -#: inc/class-settings.php:1114 +#: inc/class-settings.php:1126 msgid "By default, Ultimate Multisite only allows super admins to limit post types that are registered on the main site. This makes sense from a technical stand-point but it also forces you to have plugins network-activated in order to be able to set limitations for their custom post types. Using this option, you can emulate the registering of a post type. This will register them on the main site and allow you to create limits for them on your products." msgstr "" -#: inc/class-settings.php:1125 +#: inc/class-settings.php:1137 msgid "Add the first post type using the button below." msgstr "" -#: inc/class-settings.php:1159 +#: inc/class-settings.php:1171 msgid "Post Type Slug" msgstr "" -#: inc/class-settings.php:1160 +#: inc/class-settings.php:1172 msgid "e.g. product" msgstr "" -#: inc/class-settings.php:1169 +#: inc/class-settings.php:1181 msgid "Post Type Label" msgstr "" -#: inc/class-settings.php:1170 +#: inc/class-settings.php:1182 msgid "e.g. Products" msgstr "" -#: inc/class-settings.php:1186 +#: inc/class-settings.php:1198 msgid "+ Add Post Type" msgstr "" -#: inc/class-settings.php:1224 +#: inc/class-settings.php:1236 msgid "Configure certain aspects of how network Sites behave." msgstr "" -#: inc/class-settings.php:1234 +#: inc/class-settings.php:1246 msgid "Default New Site Page" msgstr "" -#: inc/class-settings.php:1254 +#: inc/class-settings.php:1266 msgid "Enable Visits Limitation & Counting" msgstr "" -#: inc/class-settings.php:1255 +#: inc/class-settings.php:1267 msgid "Enabling this option will add visits limitation settings to the plans and add the functionality necessary to count site visits on the front-end." msgstr "" -#: inc/class-settings.php:1265 +#: inc/class-settings.php:1277 msgid "Enable Screenshot Generator" msgstr "" -#: inc/class-settings.php:1266 +#: inc/class-settings.php:1278 msgid "With this option is enabled, Ultimate Multisite will take a screenshot for every newly created site on your network and set the resulting image as that site's featured image. This features requires a valid license key to work and it is not supported for local sites." msgstr "" -#: inc/class-settings.php:1276 +#: inc/class-settings.php:1288 msgid "WordPress Features" msgstr "" -#: inc/class-settings.php:1277 +#: inc/class-settings.php:1289 msgid "Override default WordPress settings for network Sites." msgstr "" -#: inc/class-settings.php:1286 +#: inc/class-settings.php:1298 msgid "Enable Plugins Menu" msgstr "" -#: inc/class-settings.php:1287 +#: inc/class-settings.php:1299 msgid "Do you want to let users on the network to have access to the Plugins page, activating plugins for their sites? If this option is disabled, the customer will not be able to manage the site plugins." msgstr "" -#: inc/class-settings.php:1288 +#: inc/class-settings.php:1300 msgid "You can select which plugins the user will be able to use for each plan." msgstr "" -#: inc/class-settings.php:1298 +#: inc/class-settings.php:1310 msgid "Add New Users" msgstr "" -#: inc/class-settings.php:1299 +#: inc/class-settings.php:1311 msgid "Allow site administrators to add new users to their site via the \"Users → Add New\" page." msgstr "" -#: inc/class-settings.php:1300 +#: inc/class-settings.php:1312 msgid "You can limit the number of users allowed for each plan." msgstr "" -#: inc/class-settings.php:1310 +#: inc/class-settings.php:1322 msgid "Site Template Options" msgstr "" -#: inc/class-settings.php:1311 +#: inc/class-settings.php:1323 msgid "Configure certain aspects of how Site Templates behave." msgstr "" -#: inc/class-settings.php:1320 +#: inc/class-settings.php:1332 msgid "Allow Template Switching" msgstr "" -#: inc/class-settings.php:1321 +#: inc/class-settings.php:1333 msgid "Enabling this option will add an option on your client's dashboard to switch their site template to another one available on the catalog of available templates. The data is lost after a switch as the data from the new template is copied over." msgstr "" -#: inc/class-settings.php:1331 +#: inc/class-settings.php:1343 msgid "Allow Users to use their own Sites as Templates" msgstr "" -#: inc/class-settings.php:1332 +#: inc/class-settings.php:1344 msgid "Enabling this option will add the user own sites to the template screen, allowing them to create a new site based on the content and customizations they made previously." msgstr "" -#: inc/class-settings.php:1345 +#: inc/class-settings.php:1357 msgid "Copy Media on Template Duplication?" msgstr "" -#: inc/class-settings.php:1346 +#: inc/class-settings.php:1358 msgid "Checking this option will copy the media uploaded on the template site to the newly created site. This can be overridden on each of the plans." msgstr "" -#: inc/class-settings.php:1356 +#: inc/class-settings.php:1368 msgid "Prevent Search Engines from indexing Site Templates" msgstr "" -#: inc/class-settings.php:1357 +#: inc/class-settings.php:1369 msgid "Checking this option will discourage search engines from indexing all the Site Templates on your network." msgstr "" -#: inc/class-settings.php:1383 +#: inc/class-settings.php:1395 msgid "Payment Settings" msgstr "" -#: inc/class-settings.php:1394 +#: inc/class-settings.php:1406 msgid "Force Auto-Renew" msgstr "" -#: inc/class-settings.php:1395 +#: inc/class-settings.php:1407 msgid "Enable this option if you want to make sure memberships are created with auto-renew activated whenever the selected gateway supports it. Disabling this option will show an auto-renew option during checkout." msgstr "" -#: inc/class-settings.php:1406 +#: inc/class-settings.php:1418 msgid "Allow Trials without Payment Method" msgstr "" -#: inc/class-settings.php:1407 +#: inc/class-settings.php:1419 msgid "By default, Ultimate Multisite asks customers to add a payment method on sign-up even if a trial period is present. Enable this option to only ask for a payment method when the trial period is over." msgstr "" -#: inc/class-settings.php:1418 +#: inc/class-settings.php:1430 msgid "Send Invoice on Payment Confirmation" msgstr "" -#: inc/class-settings.php:1419 +#: inc/class-settings.php:1431 msgid "Enabling this option will attach a PDF invoice (marked paid) with the payment confirmation email. This option does not apply to the Manual Gateway, which sends invoices regardless of this option." msgstr "" -#: inc/class-settings.php:1420 +#: inc/class-settings.php:1432 msgid "The invoice files will be saved on the wp-content/uploads/wu-invoices folder." msgstr "" -#: inc/class-settings.php:1430 +#: inc/class-settings.php:1442 msgid "Invoice Numbering Scheme" msgstr "" -#: inc/class-settings.php:1431 +#: inc/class-settings.php:1443 msgid "What should Ultimate Multisite use as the invoice number?" msgstr "" -#: inc/class-settings.php:1436 +#: inc/class-settings.php:1448 msgid "Payment Reference Code" msgstr "" -#: inc/class-settings.php:1437 +#: inc/class-settings.php:1449 msgid "Sequential Number" msgstr "" -#: inc/class-settings.php:1446 +#: inc/class-settings.php:1458 msgid "Next Invoice Number" msgstr "" -#: inc/class-settings.php:1447 +#: inc/class-settings.php:1459 msgid "This number will be used as the invoice number for the next invoice generated on the system. It is incremented by one every time a new invoice is created. You can change it and save it to reset the invoice sequential number to a specific value." msgstr "" -#: inc/class-settings.php:1461 +#: inc/class-settings.php:1473 msgid "Invoice Number Prefix" msgstr "" -#: inc/class-settings.php:1462 +#: inc/class-settings.php:1474 msgid "INV00" msgstr "" #. translators: %%YEAR%%, %%MONTH%%, and %%DAY%% are placeholders but are replaced before shown to the user but are used as examples. -#: inc/class-settings.php:1464 +#: inc/class-settings.php:1476 #, php-format msgid "Use %%YEAR%%, %%MONTH%%, and %%DAY%% to create a dynamic placeholder. E.g. %%YEAR%%-%%MONTH%%-INV will become %s." msgstr "" -#: inc/class-settings.php:1478 +#: inc/class-settings.php:1490 #: inc/ui/class-jumper.php:209 msgid "Payment Gateways" msgstr "" -#: inc/class-settings.php:1479 +#: inc/class-settings.php:1491 msgid "Activate and configure the installed payment gateways in this section." msgstr "" -#: inc/class-settings.php:1494 -#: inc/class-settings.php:1495 +#: inc/class-settings.php:1506 +#: inc/class-settings.php:1507 #: inc/list-tables/class-broadcast-list-table.php:481 #: inc/list-tables/class-email-list-table.php:40 #: inc/ui/class-jumper.php:211 msgid "Emails" msgstr "" -#: inc/class-settings.php:1510 -#: inc/class-settings.php:1511 +#: inc/class-settings.php:1522 +#: inc/class-settings.php:1523 #: inc/integrations/providers/closte/class-closte-domain-mapping.php:48 #: inc/integrations/providers/cloudflare/class-cloudflare-domain-mapping.php:47 #: inc/integrations/providers/cloudways/class-cloudways-domain-mapping.php:47 @@ -9449,139 +9461,139 @@ msgstr "" msgid "Domain Mapping" msgstr "" -#: inc/class-settings.php:1526 -#: inc/class-settings.php:1527 +#: inc/class-settings.php:1538 +#: inc/class-settings.php:1539 msgid "Single Sign-On" msgstr "" -#: inc/class-settings.php:1552 +#: inc/class-settings.php:1564 msgid "Hosting or Panel Providers" msgstr "" -#: inc/class-settings.php:1553 +#: inc/class-settings.php:1565 msgid "Configure and manage the integration with your Hosting or Panel Provider." msgstr "" -#: inc/class-settings.php:1569 +#: inc/class-settings.php:1581 msgid "Import/Export" msgstr "" -#: inc/class-settings.php:1570 +#: inc/class-settings.php:1582 msgid "Export your settings to a JSON file or import settings from a previously exported file." msgstr "" -#: inc/class-settings.php:1581 -#: inc/class-settings.php:1606 +#: inc/class-settings.php:1593 +#: inc/class-settings.php:1618 msgid "Export Settings" msgstr "" -#: inc/class-settings.php:1582 +#: inc/class-settings.php:1594 msgid "Download all your Ultimate Multisite settings as a JSON file for backup or migration purposes." msgstr "" -#: inc/class-settings.php:1594 +#: inc/class-settings.php:1606 msgid "The exported file will contain all ultimate multisite settings defined on this page. This includes general settings, payment gateway configurations, email settings, domain mapping settings, and all other plugin configurations. It does not include products, sites, domains, customers and other entities." msgstr "" -#: inc/class-settings.php:1625 +#: inc/class-settings.php:1637 msgid "Upload a previously exported JSON file to restore settings." msgstr "" -#: inc/class-settings.php:1638 +#: inc/class-settings.php:1650 msgid "Import and Replace All Settings" msgstr "" -#: inc/class-settings.php:1662 +#: inc/class-settings.php:1674 msgid "Importing settings will replace ALL current settings with the values from the uploaded file. This action cannot be undone. We recommend exporting your current settings as a backup before importing." msgstr "" -#: inc/class-settings.php:1690 +#: inc/class-settings.php:1702 msgid "Miscellaneous" msgstr "" -#: inc/class-settings.php:1691 +#: inc/class-settings.php:1703 msgid "Other options that do not fit anywhere else." msgstr "" -#: inc/class-settings.php:1702 +#: inc/class-settings.php:1714 msgid "Hide UI Tours" msgstr "" -#: inc/class-settings.php:1703 +#: inc/class-settings.php:1715 msgid "The UI tours showed by Ultimate Multisite should permanently hide themselves after being seen but if they persist for whatever reason, toggle this option to force them into their viewed state - which will prevent them from showing up again." msgstr "" -#: inc/class-settings.php:1715 +#: inc/class-settings.php:1727 msgid "Disable \"Hover to Zoom\"" msgstr "" -#: inc/class-settings.php:1716 +#: inc/class-settings.php:1728 msgid "By default, Ultimate Multisite adds a \"hover to zoom\" feature, allowing network admins to see larger version of site screenshots and other images across the UI in full-size when hovering over them. You can disable that feature here. Preview tags like the above are not affected." msgstr "" -#: inc/class-settings.php:1726 +#: inc/class-settings.php:1738 msgid "Logging" msgstr "" -#: inc/class-settings.php:1727 +#: inc/class-settings.php:1739 msgid "Log Ultimate Multisite data. This is useful for debugging purposes." msgstr "" -#: inc/class-settings.php:1736 +#: inc/class-settings.php:1748 msgid "Logging Level" msgstr "" -#: inc/class-settings.php:1737 +#: inc/class-settings.php:1749 msgid "Select the level of logging you want to use." msgstr "" -#: inc/class-settings.php:1741 +#: inc/class-settings.php:1753 msgid "PHP Default" msgstr "" -#: inc/class-settings.php:1743 +#: inc/class-settings.php:1755 msgid "Errors Only" msgstr "" -#: inc/class-settings.php:1744 +#: inc/class-settings.php:1756 msgid "Everything" msgstr "" -#: inc/class-settings.php:1754 +#: inc/class-settings.php:1766 msgid "Change the plugin and wordpress behavior." msgstr "" -#: inc/class-settings.php:1769 +#: inc/class-settings.php:1781 msgid "Run Migration Again" msgstr "" -#: inc/class-settings.php:1771 +#: inc/class-settings.php:1783 msgid "Rerun the Migration Wizard if you experience data-loss after migrate." msgstr "" -#: inc/class-settings.php:1774 +#: inc/class-settings.php:1786 msgid "Important: This process can have unexpected behavior with your current Ultimo models.
We recommend that you create a backup before continue." msgstr "" -#: inc/class-settings.php:1777 +#: inc/class-settings.php:1789 msgid "Migrate" msgstr "" -#: inc/class-settings.php:1800 +#: inc/class-settings.php:1812 msgid "Security Mode" msgstr "" #. Translators: Placeholder adds the security mode key and current site url with query string -#: inc/class-settings.php:1802 +#: inc/class-settings.php:1814 #, php-format msgid "Only Ultimate Multisite and other must-use plugins will run on your WordPress install while this option is enabled.
Important: Copy the following URL to disable security mode if something goes wrong and this page becomes unavailable:%2$s
" msgstr "" -#: inc/class-settings.php:1813 +#: inc/class-settings.php:1825 msgid "Remove Data on Uninstall" msgstr "" -#: inc/class-settings.php:1814 +#: inc/class-settings.php:1826 msgid "Remove all saved data for Ultimate Multisite when the plugin is uninstalled." msgstr "" @@ -16316,35 +16328,35 @@ msgid "Plugin not found." msgstr "" #. translators: %s is the name of a host provider (e.g. Cloudways, WPMUDev, Closte...). -#: inc/integrations/class-integration-registry.php:357 +#: inc/integrations/class-integration-registry.php:358 #, php-format msgid "%s Integration" msgstr "" -#: inc/integrations/class-integration-registry.php:361 +#: inc/integrations/class-integration-registry.php:362 msgid "Go to the setup wizard to setup this integration." msgstr "" #. translators: %1$s will be replaced with the integration title. E.g. RunCloud -#: inc/integrations/class-integration-registry.php:393 +#: inc/integrations/class-integration-registry.php:394 #, php-format msgid "It looks like you are using %1$s as your hosting provider, yet the %1$s integration module is not active. In order for the domain mapping integration to work with %1$s, you might want to activate that module." msgstr "" #. translators: %s is the integration name. -#: inc/integrations/class-integration-registry.php:398 +#: inc/integrations/class-integration-registry.php:399 #, php-format msgid "Activate %s" msgstr "" #. translators: %s is the integration name. -#: inc/integrations/class-integration-registry.php:411 +#: inc/integrations/class-integration-registry.php:412 #, php-format msgid "The %s integration module is active but not properly configured. Please complete the setup." msgstr "" #. translators: %s is the integration name. -#: inc/integrations/class-integration-registry.php:416 +#: inc/integrations/class-integration-registry.php:417 #, php-format msgid "Setup %s" msgstr "" @@ -22282,7 +22294,7 @@ msgid "Use Site Tools or contact support for cPanel credentials" msgstr "" #: views/wizards/host-integrations/cpanel-instructions.php:134 -msgid "A2 Hosting, InMotion:" +msgid "Hosting.com, InMotion:" msgstr "" #: views/wizards/host-integrations/cpanel-instructions.php:134 diff --git a/package.json b/package.json index e730e6752..3cd69fdde 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "2.4.11-beta.4", + "version": "2.4.11-beta.3", "name": "ultimate-multisite", "title": "Ultimate Multisite", "homepage": "https://ultimatemultisite.com/", diff --git a/tests/WP_Ultimo/Admin_Pages/Customer_List_Admin_Page_Test.php b/tests/WP_Ultimo/Admin_Pages/Customer_List_Admin_Page_Test.php new file mode 100644 index 000000000..df48fd41e --- /dev/null +++ b/tests/WP_Ultimo/Admin_Pages/Customer_List_Admin_Page_Test.php @@ -0,0 +1,156 @@ +page = new Customer_List_Admin_Page(); + } + + /** + * Test page id is correct. + */ + public function test_page_id(): void { + $reflection = new \ReflectionClass($this->page); + $property = $reflection->getProperty('id'); + $property->setAccessible(true); + + $this->assertEquals('wp-ultimo-customers', $property->getValue($this->page)); + } + + /** + * Test page type is submenu. + */ + public function test_page_type(): void { + $reflection = new \ReflectionClass($this->page); + $property = $reflection->getProperty('type'); + $property->setAccessible(true); + + $this->assertEquals('submenu', $property->getValue($this->page)); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->page->get_title(); + + $this->assertIsString($title); + $this->assertEquals('Customers', $title); + } + + /** + * Test get_menu_title returns string. + */ + public function test_get_menu_title(): void { + $title = $this->page->get_menu_title(); + + $this->assertIsString($title); + $this->assertEquals('Customers', $title); + } + + /** + * Test get_submenu_title returns string. + */ + public function test_get_submenu_title(): void { + $title = $this->page->get_submenu_title(); + + $this->assertIsString($title); + $this->assertEquals('Customers', $title); + } + + /** + * Test get_labels returns array. + */ + public function test_get_labels(): void { + $labels = $this->page->get_labels(); + + $this->assertIsArray($labels); + $this->assertArrayHasKey('deleted_message', $labels); + $this->assertArrayHasKey('search_label', $labels); + } + + /** + * Test action_links returns array. + */ + public function test_action_links(): void { + $links = $this->page->action_links(); + + $this->assertIsArray($links); + $this->assertCount(2, $links); + } + + /** + * Test action_links has add customer link. + */ + public function test_action_links_add_customer(): void { + $links = $this->page->action_links(); + + $this->assertEquals('Add Customer', $links[0]['label']); + $this->assertArrayHasKey('url', $links[0]); + $this->assertArrayHasKey('icon', $links[0]); + } + + /** + * Test action_links has export link. + */ + public function test_action_links_export(): void { + $links = $this->page->action_links(); + + $this->assertEquals('Export as CSV', $links[1]['label']); + $this->assertStringContainsString('wu_export_customers', $links[1]['url']); + } + + /** + * Test table returns list table instance. + */ + public function test_table(): void { + $table = $this->page->table(); + + $this->assertInstanceOf(\WP_Ultimo\List_Tables\Customer_List_Table::class, $table); + } + + /** + * Test supported_panels contains network_admin_menu. + */ + public function test_supported_panels(): void { + $reflection = new \ReflectionClass($this->page); + $property = $reflection->getProperty('supported_panels'); + $property->setAccessible(true); + + $panels = $property->getValue($this->page); + $this->assertArrayHasKey('network_admin_menu', $panels); + $this->assertEquals('wu_read_customers', $panels['network_admin_menu']); + } + + /** + * Test badge_count is zero. + */ + public function test_badge_count(): void { + $reflection = new \ReflectionClass($this->page); + $property = $reflection->getProperty('badge_count'); + $property->setAccessible(true); + + $this->assertEquals(0, $property->getValue($this->page)); + } +} diff --git a/tests/WP_Ultimo/Admin_Pages/Payment_List_Admin_Page_Test.php b/tests/WP_Ultimo/Admin_Pages/Payment_List_Admin_Page_Test.php new file mode 100644 index 000000000..0d7a0d595 --- /dev/null +++ b/tests/WP_Ultimo/Admin_Pages/Payment_List_Admin_Page_Test.php @@ -0,0 +1,146 @@ +page = new Payment_List_Admin_Page(); + } + + /** + * Test page id is correct. + */ + public function test_page_id(): void { + $reflection = new \ReflectionClass($this->page); + $property = $reflection->getProperty('id'); + $property->setAccessible(true); + + $this->assertEquals('wp-ultimo-payments', $property->getValue($this->page)); + } + + /** + * Test page type is submenu. + */ + public function test_page_type(): void { + $reflection = new \ReflectionClass($this->page); + $property = $reflection->getProperty('type'); + $property->setAccessible(true); + + $this->assertEquals('submenu', $property->getValue($this->page)); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->page->get_title(); + + $this->assertIsString($title); + $this->assertEquals('Payments', $title); + } + + /** + * Test get_menu_title returns string. + */ + public function test_get_menu_title(): void { + $title = $this->page->get_menu_title(); + + $this->assertIsString($title); + $this->assertEquals('Payments', $title); + } + + /** + * Test get_submenu_title returns string. + */ + public function test_get_submenu_title(): void { + $title = $this->page->get_submenu_title(); + + $this->assertIsString($title); + $this->assertEquals('Payments', $title); + } + + /** + * Test get_labels returns array. + */ + public function test_get_labels(): void { + $labels = $this->page->get_labels(); + + $this->assertIsArray($labels); + $this->assertArrayHasKey('deleted_message', $labels); + $this->assertArrayHasKey('search_label', $labels); + } + + /** + * Test action_links returns array. + */ + public function test_action_links(): void { + $links = $this->page->action_links(); + + $this->assertIsArray($links); + $this->assertCount(1, $links); + } + + /** + * Test action_links has add payment link. + */ + public function test_action_links_add_payment(): void { + $links = $this->page->action_links(); + + $this->assertEquals('Add Payment', $links[0]['label']); + $this->assertArrayHasKey('url', $links[0]); + $this->assertArrayHasKey('icon', $links[0]); + } + + /** + * Test table returns list table instance. + */ + public function test_table(): void { + $table = $this->page->table(); + + $this->assertInstanceOf(\WP_Ultimo\List_Tables\Payment_List_Table::class, $table); + } + + /** + * Test supported_panels contains network_admin_menu. + */ + public function test_supported_panels(): void { + $reflection = new \ReflectionClass($this->page); + $property = $reflection->getProperty('supported_panels'); + $property->setAccessible(true); + + $panels = $property->getValue($this->page); + $this->assertArrayHasKey('network_admin_menu', $panels); + $this->assertEquals('wu_read_payments', $panels['network_admin_menu']); + } + + /** + * Test badge_count is zero. + */ + public function test_badge_count(): void { + $reflection = new \ReflectionClass($this->page); + $property = $reflection->getProperty('badge_count'); + $property->setAccessible(true); + + $this->assertEquals(0, $property->getValue($this->page)); + } +} diff --git a/tests/WP_Ultimo/Admin_Pages/Product_List_Admin_Page_Test.php b/tests/WP_Ultimo/Admin_Pages/Product_List_Admin_Page_Test.php new file mode 100644 index 000000000..f95399cf8 --- /dev/null +++ b/tests/WP_Ultimo/Admin_Pages/Product_List_Admin_Page_Test.php @@ -0,0 +1,146 @@ +page = new Product_List_Admin_Page(); + } + + /** + * Test page id is correct. + */ + public function test_page_id(): void { + $reflection = new \ReflectionClass($this->page); + $property = $reflection->getProperty('id'); + $property->setAccessible(true); + + $this->assertEquals('wp-ultimo-products', $property->getValue($this->page)); + } + + /** + * Test page type is submenu. + */ + public function test_page_type(): void { + $reflection = new \ReflectionClass($this->page); + $property = $reflection->getProperty('type'); + $property->setAccessible(true); + + $this->assertEquals('submenu', $property->getValue($this->page)); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->page->get_title(); + + $this->assertIsString($title); + $this->assertEquals('Products', $title); + } + + /** + * Test get_menu_title returns string. + */ + public function test_get_menu_title(): void { + $title = $this->page->get_menu_title(); + + $this->assertIsString($title); + $this->assertEquals('Products', $title); + } + + /** + * Test get_submenu_title returns string. + */ + public function test_get_submenu_title(): void { + $title = $this->page->get_submenu_title(); + + $this->assertIsString($title); + $this->assertEquals('Products', $title); + } + + /** + * Test get_labels returns array. + */ + public function test_get_labels(): void { + $labels = $this->page->get_labels(); + + $this->assertIsArray($labels); + $this->assertArrayHasKey('deleted_message', $labels); + $this->assertArrayHasKey('search_label', $labels); + } + + /** + * Test action_links returns array. + */ + public function test_action_links(): void { + $links = $this->page->action_links(); + + $this->assertIsArray($links); + $this->assertCount(1, $links); + } + + /** + * Test action_links has add product link. + */ + public function test_action_links_add_product(): void { + $links = $this->page->action_links(); + + $this->assertEquals('Add Product', $links[0]['label']); + $this->assertArrayHasKey('url', $links[0]); + $this->assertArrayHasKey('icon', $links[0]); + } + + /** + * Test table returns list table instance. + */ + public function test_table(): void { + $table = $this->page->table(); + + $this->assertInstanceOf(\WP_Ultimo\List_Tables\Product_List_Table::class, $table); + } + + /** + * Test supported_panels contains network_admin_menu. + */ + public function test_supported_panels(): void { + $reflection = new \ReflectionClass($this->page); + $property = $reflection->getProperty('supported_panels'); + $property->setAccessible(true); + + $panels = $property->getValue($this->page); + $this->assertArrayHasKey('network_admin_menu', $panels); + $this->assertEquals('wu_read_products', $panels['network_admin_menu']); + } + + /** + * Test badge_count is zero. + */ + public function test_badge_count(): void { + $reflection = new \ReflectionClass($this->page); + $property = $reflection->getProperty('badge_count'); + $property->setAccessible(true); + + $this->assertEquals(0, $property->getValue($this->page)); + } +} diff --git a/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Checkbox_Test.php b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Checkbox_Test.php new file mode 100644 index 000000000..148c31f43 --- /dev/null +++ b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Checkbox_Test.php @@ -0,0 +1,167 @@ +field = new Signup_Field_Checkbox(); + } + + /** + * Test get_type returns checkbox. + */ + public function test_get_type(): void { + $this->assertEquals('checkbox', $this->field->get_type()); + } + + /** + * Test is_required returns false. + */ + public function test_is_required(): void { + $this->assertFalse($this->field->is_required()); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->field->get_title(); + $this->assertIsString($title); + $this->assertEquals('Checkbox', $title); + } + + /** + * Test get_description returns string. + */ + public function test_get_description(): void { + $description = $this->field->get_description(); + $this->assertIsString($description); + $this->assertNotEmpty($description); + } + + /** + * Test get_tooltip returns string. + */ + public function test_get_tooltip(): void { + $tooltip = $this->field->get_tooltip(); + $this->assertIsString($tooltip); + $this->assertNotEmpty($tooltip); + } + + /** + * Test get_icon returns dashicon class. + */ + public function test_get_icon(): void { + $icon = $this->field->get_icon(); + $this->assertIsString($icon); + $this->assertStringContainsString('dashicons', $icon); + } + + /** + * Test is_user_field returns false. + */ + public function test_is_user_field(): void { + $this->assertFalse($this->field->is_user_field()); + } + + /** + * Test is_site_field returns false. + */ + public function test_is_site_field(): void { + $this->assertFalse($this->field->is_site_field()); + } + + /** + * Test defaults returns array. + */ + public function test_defaults(): void { + $defaults = $this->field->defaults(); + $this->assertIsArray($defaults); + } + + /** + * Test default_fields contains expected fields. + */ + public function test_default_fields(): void { + $fields = $this->field->default_fields(); + $this->assertIsArray($fields); + $this->assertContains('id', $fields); + $this->assertContains('name', $fields); + $this->assertContains('tooltip', $fields); + $this->assertContains('required', $fields); + } + + /** + * Test get_fields returns array with expected keys. + */ + public function test_get_fields(): void { + $fields = $this->field->get_fields(); + $this->assertIsArray($fields); + $this->assertArrayHasKey('default_state', $fields); + } + + /** + * Test to_fields_array returns proper structure. + */ + public function test_to_fields_array(): void { + $attributes = [ + 'id' => 'terms_checkbox', + 'name' => 'I agree to the terms', + 'tooltip' => 'You must agree to continue', + 'required' => true, + 'element_classes' => 'custom-class', + 'default_state' => false, + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('terms_checkbox', $fields); + $this->assertEquals('checkbox', $fields['terms_checkbox']['type']); + $this->assertEquals('I agree to the terms', $fields['terms_checkbox']['name']); + $this->assertTrue($fields['terms_checkbox']['required']); + } + + /** + * Test to_fields_array with default state enabled. + */ + public function test_to_fields_array_with_default_state(): void { + $attributes = [ + 'id' => 'subscribe_checkbox', + 'name' => 'Subscribe to newsletter', + 'tooltip' => '', + 'required' => false, + 'element_classes' => '', + 'default_state' => true, + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('subscribe_checkbox', $fields); + $this->assertArrayHasKey('html_attr', $fields['subscribe_checkbox']); + $this->assertEquals('checked', $fields['subscribe_checkbox']['html_attr']['checked']); + } +} diff --git a/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Color_Test.php b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Color_Test.php new file mode 100644 index 000000000..a418569b7 --- /dev/null +++ b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Color_Test.php @@ -0,0 +1,145 @@ +field = new Signup_Field_Color(); + } + + /** + * Test get_type returns color_picker. + */ + public function test_get_type(): void { + $this->assertEquals('color_picker', $this->field->get_type()); + } + + /** + * Test is_required returns false. + */ + public function test_is_required(): void { + $this->assertFalse($this->field->is_required()); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->field->get_title(); + $this->assertIsString($title); + $this->assertEquals('Color', $title); + } + + /** + * Test get_description returns string. + */ + public function test_get_description(): void { + $description = $this->field->get_description(); + $this->assertIsString($description); + $this->assertNotEmpty($description); + } + + /** + * Test get_tooltip returns string. + */ + public function test_get_tooltip(): void { + $tooltip = $this->field->get_tooltip(); + $this->assertIsString($tooltip); + $this->assertNotEmpty($tooltip); + } + + /** + * Test get_icon returns dashicon class. + */ + public function test_get_icon(): void { + $icon = $this->field->get_icon(); + $this->assertIsString($icon); + $this->assertStringContainsString('dashicons', $icon); + } + + /** + * Test is_user_field returns false. + */ + public function test_is_user_field(): void { + $this->assertFalse($this->field->is_user_field()); + } + + /** + * Test is_site_field returns false. + */ + public function test_is_site_field(): void { + $this->assertFalse($this->field->is_site_field()); + } + + /** + * Test defaults returns array. + */ + public function test_defaults(): void { + $defaults = $this->field->defaults(); + $this->assertIsArray($defaults); + } + + /** + * Test default_fields contains expected fields. + */ + public function test_default_fields(): void { + $fields = $this->field->default_fields(); + $this->assertIsArray($fields); + $this->assertContains('id', $fields); + $this->assertContains('name', $fields); + $this->assertContains('required', $fields); + } + + /** + * Test get_fields returns array with default_value. + */ + public function test_get_fields(): void { + $fields = $this->field->get_fields(); + $this->assertIsArray($fields); + $this->assertArrayHasKey('default_value', $fields); + } + + /** + * Test to_fields_array returns proper structure. + */ + public function test_to_fields_array(): void { + $attributes = [ + 'id' => 'brand_color', + 'name' => 'Brand Color', + 'placeholder' => '#ffffff', + 'tooltip' => 'Pick your brand color', + 'default_value' => '#0000ff', + 'required' => false, + 'element_classes' => 'color-class', + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('brand_color', $fields); + $this->assertEquals('color', $fields['brand_color']['type']); + $this->assertEquals('Brand Color', $fields['brand_color']['name']); + $this->assertEquals('#0000ff', $fields['brand_color']['default']); + } +} diff --git a/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Discount_Code_Test.php b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Discount_Code_Test.php new file mode 100644 index 000000000..631635f76 --- /dev/null +++ b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Discount_Code_Test.php @@ -0,0 +1,155 @@ +field = new Signup_Field_Discount_Code(); + } + + /** + * Test get_type returns discount_code. + */ + public function test_get_type(): void { + $this->assertEquals('discount_code', $this->field->get_type()); + } + + /** + * Test is_required returns false. + */ + public function test_is_required(): void { + $this->assertFalse($this->field->is_required()); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->field->get_title(); + $this->assertIsString($title); + $this->assertEquals('Coupon Code', $title); + } + + /** + * Test get_description returns string. + */ + public function test_get_description(): void { + $description = $this->field->get_description(); + $this->assertIsString($description); + $this->assertNotEmpty($description); + } + + /** + * Test get_tooltip returns string. + */ + public function test_get_tooltip(): void { + $tooltip = $this->field->get_tooltip(); + $this->assertIsString($tooltip); + $this->assertNotEmpty($tooltip); + } + + /** + * Test get_icon returns dashicon class. + */ + public function test_get_icon(): void { + $icon = $this->field->get_icon(); + $this->assertIsString($icon); + $this->assertStringContainsString('dashicons', $icon); + } + + /** + * Test is_user_field returns false. + */ + public function test_is_user_field(): void { + $this->assertFalse($this->field->is_user_field()); + } + + /** + * Test is_site_field returns false. + */ + public function test_is_site_field(): void { + $this->assertFalse($this->field->is_site_field()); + } + + /** + * Test defaults returns array with expected keys. + */ + public function test_defaults(): void { + $defaults = $this->field->defaults(); + $this->assertIsArray($defaults); + $this->assertArrayHasKey('placeholder', $defaults); + $this->assertArrayHasKey('default', $defaults); + } + + /** + * Test default_fields contains expected fields. + */ + public function test_default_fields(): void { + $fields = $this->field->default_fields(); + $this->assertIsArray($fields); + $this->assertContains('name', $fields); + $this->assertContains('placeholder', $fields); + $this->assertContains('tooltip', $fields); + } + + /** + * Test force_attributes returns expected values. + */ + public function test_force_attributes(): void { + $forced = $this->field->force_attributes(); + $this->assertIsArray($forced); + $this->assertArrayHasKey('id', $forced); + $this->assertEquals('discount_code', $forced['id']); + } + + /** + * Test get_fields returns empty array. + */ + public function test_get_fields(): void { + $fields = $this->field->get_fields(); + $this->assertIsArray($fields); + $this->assertEmpty($fields); + } + + /** + * Test to_fields_array returns proper structure. + */ + public function test_to_fields_array(): void { + $attributes = [ + 'id' => 'discount_code', + 'name' => 'Coupon Code', + 'placeholder' => 'Enter coupon', + 'tooltip' => 'Enter your discount code', + 'default' => '', + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('discount_code_checkbox', $fields); + $this->assertArrayHasKey('discount_code', $fields); + $this->assertEquals('toggle', $fields['discount_code_checkbox']['type']); + $this->assertEquals('text', $fields['discount_code']['type']); + } +} diff --git a/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Email_Test.php b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Email_Test.php new file mode 100644 index 000000000..5d50a9fd6 --- /dev/null +++ b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Email_Test.php @@ -0,0 +1,101 @@ +field = new Signup_Field_Email(); + } + + /** + * Test get_type returns email. + */ + public function test_get_type(): void { + $this->assertEquals('email', $this->field->get_type()); + } + + /** + * Test is_required returns true. + */ + public function test_is_required(): void { + $this->assertTrue($this->field->is_required()); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->field->get_title(); + $this->assertIsString($title); + $this->assertNotEmpty($title); + } + + /** + * Test get_description returns string. + */ + public function test_get_description(): void { + $description = $this->field->get_description(); + $this->assertIsString($description); + $this->assertNotEmpty($description); + } + + /** + * Test get_icon returns dashicon class. + */ + public function test_get_icon(): void { + $icon = $this->field->get_icon(); + $this->assertIsString($icon); + $this->assertStringContainsString('dashicons', $icon); + } + + /** + * Test is_user_field method exists and returns bool. + */ + public function test_is_user_field(): void { + $result = $this->field->is_user_field(); + $this->assertIsBool($result); + } + + /** + * Test is_site_field returns false. + */ + public function test_is_site_field(): void { + $this->assertFalse($this->field->is_site_field()); + } + + /** + * Test default_fields contains expected fields. + */ + public function test_default_fields(): void { + $fields = $this->field->default_fields(); + $this->assertIsArray($fields); + } + + /** + * Test get_fields returns array. + */ + public function test_get_fields(): void { + $fields = $this->field->get_fields(); + $this->assertIsArray($fields); + } +} diff --git a/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Hidden_Test.php b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Hidden_Test.php new file mode 100644 index 000000000..284fe1c90 --- /dev/null +++ b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Hidden_Test.php @@ -0,0 +1,156 @@ +field = new Signup_Field_Hidden(); + } + + /** + * Test get_type returns hidden. + */ + public function test_get_type(): void { + $this->assertEquals('hidden', $this->field->get_type()); + } + + /** + * Test is_required returns false. + */ + public function test_is_required(): void { + $this->assertFalse($this->field->is_required()); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->field->get_title(); + $this->assertIsString($title); + $this->assertEquals('Hidden Field', $title); + } + + /** + * Test get_description returns string. + */ + public function test_get_description(): void { + $description = $this->field->get_description(); + $this->assertIsString($description); + $this->assertNotEmpty($description); + } + + /** + * Test get_tooltip returns string. + */ + public function test_get_tooltip(): void { + $tooltip = $this->field->get_tooltip(); + $this->assertIsString($tooltip); + $this->assertNotEmpty($tooltip); + } + + /** + * Test get_icon returns dashicon class. + */ + public function test_get_icon(): void { + $icon = $this->field->get_icon(); + $this->assertIsString($icon); + $this->assertStringContainsString('dashicons', $icon); + } + + /** + * Test is_user_field returns false. + */ + public function test_is_user_field(): void { + $this->assertFalse($this->field->is_user_field()); + } + + /** + * Test is_site_field returns false. + */ + public function test_is_site_field(): void { + $this->assertFalse($this->field->is_site_field()); + } + + /** + * Test defaults returns array with from_request. + */ + public function test_defaults(): void { + $defaults = $this->field->defaults(); + $this->assertIsArray($defaults); + $this->assertArrayHasKey('from_request', $defaults); + $this->assertTrue($defaults['from_request']); + } + + /** + * Test default_fields contains expected fields. + */ + public function test_default_fields(): void { + $fields = $this->field->default_fields(); + $this->assertIsArray($fields); + $this->assertContains('id', $fields); + $this->assertContains('save_as', $fields); + } + + /** + * Test get_fields returns array with fixed_value. + */ + public function test_get_fields(): void { + $fields = $this->field->get_fields(); + $this->assertIsArray($fields); + $this->assertArrayHasKey('fixed_value', $fields); + } + + /** + * Test to_fields_array returns proper structure. + */ + public function test_to_fields_array(): void { + $attributes = [ + 'id' => 'tracking_id', + 'element_classes' => 'hidden-field', + 'fixed_value' => 'abc123', + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('tracking_id', $fields); + $this->assertEquals('hidden', $fields['tracking_id']['type']); + $this->assertEquals('tracking_id', $fields['tracking_id']['id']); + } + + /** + * Test get_value returns fixed_value when no other value. + */ + public function test_get_value_returns_fixed_value(): void { + $attributes = [ + 'id' => 'test_hidden', + 'fixed_value' => 'fixed_test_value', + ]; + + $this->field->set_attributes($attributes); + $value = $this->field->get_value(); + + $this->assertEquals('fixed_test_value', $value); + } +} diff --git a/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Password_Test.php b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Password_Test.php new file mode 100644 index 000000000..87064f7a1 --- /dev/null +++ b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Password_Test.php @@ -0,0 +1,211 @@ +field = new Signup_Field_Password(); + } + + /** + * Test get_type returns password. + */ + public function test_get_type(): void { + $this->assertEquals('password', $this->field->get_type()); + } + + /** + * Test is_required returns true. + */ + public function test_is_required(): void { + $this->assertTrue($this->field->is_required()); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->field->get_title(); + $this->assertIsString($title); + $this->assertEquals('Password', $title); + } + + /** + * Test get_description returns string. + */ + public function test_get_description(): void { + $description = $this->field->get_description(); + $this->assertIsString($description); + $this->assertNotEmpty($description); + } + + /** + * Test get_tooltip returns string. + */ + public function test_get_tooltip(): void { + $tooltip = $this->field->get_tooltip(); + $this->assertIsString($tooltip); + $this->assertNotEmpty($tooltip); + } + + /** + * Test get_icon returns dashicon class. + */ + public function test_get_icon(): void { + $icon = $this->field->get_icon(); + $this->assertIsString($icon); + $this->assertStringContainsString('dashicons', $icon); + } + + /** + * Test is_user_field returns true. + */ + public function test_is_user_field(): void { + $this->assertTrue($this->field->is_user_field()); + } + + /** + * Test is_site_field returns false. + */ + public function test_is_site_field(): void { + $this->assertFalse($this->field->is_site_field()); + } + + /** + * Test defaults returns array with expected keys. + */ + public function test_defaults(): void { + $defaults = $this->field->defaults(); + $this->assertIsArray($defaults); + $this->assertArrayHasKey('password_confirm_field', $defaults); + $this->assertArrayHasKey('password_confirm_label', $defaults); + } + + /** + * Test default_fields contains expected fields. + */ + public function test_default_fields(): void { + $fields = $this->field->default_fields(); + $this->assertIsArray($fields); + $this->assertContains('name', $fields); + $this->assertContains('placeholder', $fields); + $this->assertContains('tooltip', $fields); + } + + /** + * Test force_attributes returns expected values. + */ + public function test_force_attributes(): void { + $forced = $this->field->force_attributes(); + $this->assertIsArray($forced); + $this->assertArrayHasKey('id', $forced); + $this->assertEquals('password', $forced['id']); + $this->assertArrayHasKey('required', $forced); + $this->assertTrue($forced['required']); + } + + /** + * Test get_fields returns array with expected keys. + */ + public function test_get_fields(): void { + $fields = $this->field->get_fields(); + $this->assertIsArray($fields); + $this->assertArrayHasKey('password_strength_meter', $fields); + $this->assertArrayHasKey('password_confirm_field', $fields); + } + + /** + * Test to_fields_array returns empty when user is logged in. + */ + public function test_to_fields_array_logged_in_user(): void { + // Create and log in a user + $user_id = self::factory()->user->create(); + wp_set_current_user($user_id); + + $attributes = [ + 'name' => 'Password', + 'placeholder' => 'Enter password', + 'tooltip' => 'Your password', + 'password_strength_meter' => true, + 'password_confirm_field' => false, + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertEmpty($fields); + + // Clean up + wp_set_current_user(0); + } + + /** + * Test to_fields_array returns password field when not logged in. + */ + public function test_to_fields_array_not_logged_in(): void { + // Make sure user is not logged in + wp_set_current_user(0); + + $attributes = [ + 'name' => 'Password', + 'placeholder' => 'Enter password', + 'tooltip' => 'Your password', + 'password_strength_meter' => true, + 'password_confirm_field' => false, + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('password', $fields); + $this->assertEquals('password', $fields['password']['type']); + $this->assertEquals('Password', $fields['password']['name']); + } + + /** + * Test to_fields_array includes confirm field when enabled. + */ + public function test_to_fields_array_with_confirm_field(): void { + // Make sure user is not logged in + wp_set_current_user(0); + + $attributes = [ + 'name' => 'Password', + 'placeholder' => 'Enter password', + 'tooltip' => 'Your password', + 'password_strength_meter' => true, + 'password_confirm_field' => true, + 'password_confirm_label' => 'Confirm Password', + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('password', $fields); + $this->assertArrayHasKey('password_conf', $fields); + $this->assertEquals('Confirm Password', $fields['password_conf']['name']); + } +} diff --git a/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Select_Test.php b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Select_Test.php new file mode 100644 index 000000000..f56fcc22d --- /dev/null +++ b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Select_Test.php @@ -0,0 +1,180 @@ +field = new Signup_Field_Select(); + } + + /** + * Test get_type returns select. + */ + public function test_get_type(): void { + $this->assertEquals('select', $this->field->get_type()); + } + + /** + * Test is_required returns false. + */ + public function test_is_required(): void { + $this->assertFalse($this->field->is_required()); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->field->get_title(); + $this->assertIsString($title); + $this->assertEquals('Select', $title); + } + + /** + * Test get_description returns string. + */ + public function test_get_description(): void { + $description = $this->field->get_description(); + $this->assertIsString($description); + $this->assertNotEmpty($description); + } + + /** + * Test get_tooltip returns string. + */ + public function test_get_tooltip(): void { + $tooltip = $this->field->get_tooltip(); + $this->assertIsString($tooltip); + $this->assertNotEmpty($tooltip); + } + + /** + * Test get_icon returns dashicon class. + */ + public function test_get_icon(): void { + $icon = $this->field->get_icon(); + $this->assertIsString($icon); + $this->assertStringContainsString('dashicons', $icon); + } + + /** + * Test is_user_field returns false. + */ + public function test_is_user_field(): void { + $this->assertFalse($this->field->is_user_field()); + } + + /** + * Test is_site_field returns false. + */ + public function test_is_site_field(): void { + $this->assertFalse($this->field->is_site_field()); + } + + /** + * Test defaults returns array. + */ + public function test_defaults(): void { + $defaults = $this->field->defaults(); + $this->assertIsArray($defaults); + } + + /** + * Test default_fields contains expected fields. + */ + public function test_default_fields(): void { + $fields = $this->field->default_fields(); + $this->assertIsArray($fields); + $this->assertContains('id', $fields); + $this->assertContains('name', $fields); + $this->assertContains('placeholder', $fields); + $this->assertContains('required', $fields); + } + + /** + * Test get_fields returns array with expected keys. + */ + public function test_get_fields(): void { + $fields = $this->field->get_fields(); + $this->assertIsArray($fields); + $this->assertArrayHasKey('options_header', $fields); + $this->assertArrayHasKey('options', $fields); + } + + /** + * Test to_fields_array returns proper structure. + */ + public function test_to_fields_array(): void { + $attributes = [ + 'id' => 'country_select', + 'name' => 'Select Country', + 'placeholder' => 'Choose a country', + 'tooltip' => 'Select your country', + 'default_value' => 'us', + 'required' => true, + 'element_classes' => 'select-class', + 'options' => [ + ['key' => 'us', 'label' => 'United States'], + ['key' => 'uk', 'label' => 'United Kingdom'], + ['key' => 'ca', 'label' => 'Canada'], + ], + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('country_select', $fields); + $this->assertEquals('select', $fields['country_select']['type']); + $this->assertEquals('Select Country', $fields['country_select']['name']); + $this->assertTrue($fields['country_select']['required']); + + // Check options are properly formatted + $this->assertArrayHasKey('options', $fields['country_select']); + $this->assertEquals('United States', $fields['country_select']['options']['us']); + $this->assertEquals('United Kingdom', $fields['country_select']['options']['uk']); + } + + /** + * Test to_fields_array with empty options. + */ + public function test_to_fields_array_empty_options(): void { + $attributes = [ + 'id' => 'empty_select', + 'name' => 'Empty Select', + 'placeholder' => '', + 'tooltip' => '', + 'default_value' => '', + 'required' => false, + 'element_classes' => '', + 'options' => [], + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('empty_select', $fields); + $this->assertEmpty($fields['empty_select']['options']); + } +} diff --git a/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Shortcode_Test.php b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Shortcode_Test.php new file mode 100644 index 000000000..1cbd3bd78 --- /dev/null +++ b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Shortcode_Test.php @@ -0,0 +1,145 @@ +field = new Signup_Field_Shortcode(); + } + + /** + * Test get_type returns shortcode. + */ + public function test_get_type(): void { + $this->assertEquals('shortcode', $this->field->get_type()); + } + + /** + * Test is_required returns false. + */ + public function test_is_required(): void { + $this->assertFalse($this->field->is_required()); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->field->get_title(); + $this->assertIsString($title); + $this->assertEquals('Shortcode', $title); + } + + /** + * Test get_description returns string. + */ + public function test_get_description(): void { + $description = $this->field->get_description(); + $this->assertIsString($description); + $this->assertNotEmpty($description); + } + + /** + * Test get_tooltip returns string. + */ + public function test_get_tooltip(): void { + $tooltip = $this->field->get_tooltip(); + $this->assertIsString($tooltip); + $this->assertNotEmpty($tooltip); + } + + /** + * Test get_icon returns dashicon class. + */ + public function test_get_icon(): void { + $icon = $this->field->get_icon(); + $this->assertIsString($icon); + $this->assertStringContainsString('dashicons', $icon); + } + + /** + * Test is_user_field returns false. + */ + public function test_is_user_field(): void { + $this->assertFalse($this->field->is_user_field()); + } + + /** + * Test is_site_field returns false. + */ + public function test_is_site_field(): void { + $this->assertFalse($this->field->is_site_field()); + } + + /** + * Test defaults returns array. + */ + public function test_defaults(): void { + $defaults = $this->field->defaults(); + $this->assertIsArray($defaults); + } + + /** + * Test default_fields returns empty array. + */ + public function test_default_fields(): void { + $fields = $this->field->default_fields(); + $this->assertIsArray($fields); + $this->assertEmpty($fields); + } + + /** + * Test force_attributes returns expected values. + */ + public function test_force_attributes(): void { + $forced = $this->field->force_attributes(); + $this->assertIsArray($forced); + $this->assertArrayHasKey('name', $forced); + } + + /** + * Test get_fields returns array with shortcode_code. + */ + public function test_get_fields(): void { + $fields = $this->field->get_fields(); + $this->assertIsArray($fields); + $this->assertArrayHasKey('shortcode_code', $fields); + } + + /** + * Test to_fields_array returns proper structure. + */ + public function test_to_fields_array(): void { + $attributes = [ + 'id' => 'my_shortcode', + 'shortcode_code' => '[my_shortcode]', + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('my_shortcode', $fields); + $this->assertEquals('note', $fields['my_shortcode']['type']); + } +} diff --git a/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Site_Title_Test.php b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Site_Title_Test.php new file mode 100644 index 000000000..e861f8d8b --- /dev/null +++ b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Site_Title_Test.php @@ -0,0 +1,178 @@ +field = new Signup_Field_Site_Title(); + } + + /** + * Test get_type returns site_title. + */ + public function test_get_type(): void { + $this->assertEquals('site_title', $this->field->get_type()); + } + + /** + * Test is_required returns false. + */ + public function test_is_required(): void { + $this->assertFalse($this->field->is_required()); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->field->get_title(); + $this->assertIsString($title); + $this->assertEquals('Site Title', $title); + } + + /** + * Test get_description returns string. + */ + public function test_get_description(): void { + $description = $this->field->get_description(); + $this->assertIsString($description); + $this->assertNotEmpty($description); + } + + /** + * Test get_tooltip returns string. + */ + public function test_get_tooltip(): void { + $tooltip = $this->field->get_tooltip(); + $this->assertIsString($tooltip); + $this->assertNotEmpty($tooltip); + } + + /** + * Test get_icon returns dashicon class. + */ + public function test_get_icon(): void { + $icon = $this->field->get_icon(); + $this->assertIsString($icon); + $this->assertStringContainsString('dashicons', $icon); + } + + /** + * Test is_site_field returns true. + */ + public function test_is_site_field(): void { + $this->assertTrue($this->field->is_site_field()); + } + + /** + * Test is_user_field returns false. + */ + public function test_is_user_field(): void { + $this->assertFalse($this->field->is_user_field()); + } + + /** + * Test defaults returns array with auto_generate_site_title. + */ + public function test_defaults(): void { + $defaults = $this->field->defaults(); + $this->assertIsArray($defaults); + $this->assertArrayHasKey('auto_generate_site_title', $defaults); + $this->assertFalse($defaults['auto_generate_site_title']); + } + + /** + * Test default_fields contains expected fields. + */ + public function test_default_fields(): void { + $fields = $this->field->default_fields(); + $this->assertIsArray($fields); + $this->assertContains('name', $fields); + $this->assertContains('placeholder', $fields); + $this->assertContains('tooltip', $fields); + } + + /** + * Test force_attributes returns expected values. + */ + public function test_force_attributes(): void { + $forced = $this->field->force_attributes(); + $this->assertIsArray($forced); + $this->assertArrayHasKey('id', $forced); + $this->assertEquals('site_title', $forced['id']); + $this->assertArrayHasKey('required', $forced); + $this->assertTrue($forced['required']); + } + + /** + * Test get_fields returns array with auto_generate option. + */ + public function test_get_fields(): void { + $fields = $this->field->get_fields(); + $this->assertIsArray($fields); + $this->assertArrayHasKey('auto_generate_site_title', $fields); + } + + /** + * Test to_fields_array returns text field when not auto-generating. + */ + public function test_to_fields_array_regular(): void { + $attributes = [ + 'id' => 'site_title', + 'name' => 'Site Title', + 'placeholder' => 'Enter site title', + 'tooltip' => 'Your site name', + 'auto_generate_site_title' => false, + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('site_title', $fields); + $this->assertEquals('text', $fields['site_title']['type']); + $this->assertEquals('Site Title', $fields['site_title']['name']); + $this->assertTrue($fields['site_title']['required']); + } + + /** + * Test to_fields_array returns hidden fields when auto-generating. + */ + public function test_to_fields_array_auto_generate(): void { + $attributes = [ + 'name' => 'Site Title', + 'placeholder' => 'Enter site title', + 'tooltip' => '', + 'auto_generate_site_title' => true, + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('auto_generate_site_title', $fields); + $this->assertArrayHasKey('site_title', $fields); + $this->assertEquals('hidden', $fields['site_title']['type']); + $this->assertEquals('autogenerate', $fields['site_title']['value']); + } +} diff --git a/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Site_Url_Test.php b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Site_Url_Test.php new file mode 100644 index 000000000..54cec9ea3 --- /dev/null +++ b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Site_Url_Test.php @@ -0,0 +1,239 @@ +field = new Signup_Field_Site_Url(); + } + + /** + * Test get_type returns site_url. + */ + public function test_get_type(): void { + $this->assertEquals('site_url', $this->field->get_type()); + } + + /** + * Test is_required returns false. + */ + public function test_is_required(): void { + $this->assertFalse($this->field->is_required()); + } + + /** + * Test is_site_field returns true. + */ + public function test_is_site_field(): void { + $this->assertTrue($this->field->is_site_field()); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->field->get_title(); + $this->assertIsString($title); + $this->assertEquals('Site URL', $title); + } + + /** + * Test get_description returns string. + */ + public function test_get_description(): void { + $description = $this->field->get_description(); + $this->assertIsString($description); + $this->assertNotEmpty($description); + } + + /** + * Test get_tooltip returns string. + */ + public function test_get_tooltip(): void { + $tooltip = $this->field->get_tooltip(); + $this->assertIsString($tooltip); + $this->assertNotEmpty($tooltip); + } + + /** + * Test get_icon returns dashicon class. + */ + public function test_get_icon(): void { + $icon = $this->field->get_icon(); + $this->assertIsString($icon); + $this->assertStringContainsString('dashicons', $icon); + } + + /** + * Test defaults returns array. + */ + public function test_defaults(): void { + $defaults = $this->field->defaults(); + $this->assertIsArray($defaults); + $this->assertArrayHasKey('auto_generate_site_url', $defaults); + $this->assertArrayHasKey('display_url_preview', $defaults); + $this->assertArrayHasKey('enable_domain_selection', $defaults); + $this->assertArrayHasKey('display_field_attachments', $defaults); + $this->assertArrayHasKey('available_domains', $defaults); + } + + /** + * Test default_fields returns expected fields. + */ + public function test_default_fields(): void { + $fields = $this->field->default_fields(); + $this->assertIsArray($fields); + $this->assertContains('name', $fields); + $this->assertContains('placeholder', $fields); + $this->assertContains('tooltip', $fields); + } + + /** + * Test force_attributes returns expected values. + */ + public function test_force_attributes(): void { + $forced = $this->field->force_attributes(); + $this->assertIsArray($forced); + $this->assertArrayHasKey('id', $forced); + $this->assertEquals('site_url', $forced['id']); + $this->assertArrayHasKey('required', $forced); + $this->assertTrue($forced['required']); + } + + /** + * Test get_fields returns array. + */ + public function test_get_fields(): void { + $fields = $this->field->get_fields(); + $this->assertIsArray($fields); + $this->assertArrayHasKey('auto_generate_site_url', $fields); + $this->assertArrayHasKey('display_url_preview', $fields); + $this->assertArrayHasKey('enable_domain_selection', $fields); + $this->assertArrayHasKey('available_domains', $fields); + } + + /** + * Test get_url_preview_templates returns array. + */ + public function test_get_url_preview_templates(): void { + $templates = $this->field->get_url_preview_templates(); + $this->assertIsArray($templates); + $this->assertNotEmpty($templates); + } + + /** + * Test to_fields_array with auto generate enabled. + */ + public function test_to_fields_array_auto_generate(): void { + $attributes = [ + 'id' => 'site_url', + 'name' => 'Site URL', + 'placeholder' => 'Enter site URL', + 'tooltip' => 'Your site URL', + 'auto_generate_site_url' => true, + 'display_url_preview_with_auto' => false, + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('auto_generate_site_url', $fields); + $this->assertArrayHasKey('site_url', $fields); + $this->assertEquals('hidden', $fields['site_url']['type']); + } + + /** + * Test to_fields_array with regular configuration. + */ + public function test_to_fields_array_regular(): void { + $attributes = [ + 'id' => 'site_url', + 'name' => 'Site URL', + 'placeholder' => 'Enter site URL', + 'tooltip' => 'Your site URL', + 'auto_generate_site_url' => false, + 'display_url_preview' => false, + 'display_field_attachments' => false, + 'enable_domain_selection' => false, + 'available_domains' => '', + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('site_url', $fields); + $this->assertEquals('text', $fields['site_url']['type']); + $this->assertEquals('Site URL', $fields['site_url']['name']); + } + + /** + * Test to_fields_array with domain selection enabled. + */ + public function test_to_fields_array_with_domain_selection(): void { + $attributes = [ + 'id' => 'site_url', + 'name' => 'Site URL', + 'placeholder' => 'Enter site URL', + 'tooltip' => 'Your site URL', + 'auto_generate_site_url' => false, + 'display_url_preview' => false, + 'display_field_attachments' => false, + 'enable_domain_selection' => true, + 'available_domains' => "example.com\nexample.org", + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('site_domain', $fields); + $this->assertEquals('select', $fields['site_domain']['type']); + } + + /** + * Test to_fields_array with field attachments. + */ + public function test_to_fields_array_with_attachments(): void { + $attributes = [ + 'id' => 'site_url', + 'name' => 'Site URL', + 'placeholder' => 'Enter site URL', + 'tooltip' => 'Your site URL', + 'auto_generate_site_url' => false, + 'display_url_preview' => false, + 'display_field_attachments' => true, + 'enable_domain_selection' => false, + 'available_domains' => '', + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('site_url', $fields); + $this->assertArrayHasKey('prefix', $fields['site_url']); + $this->assertArrayHasKey('suffix', $fields['site_url']); + } +} diff --git a/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Submit_Button_Test.php b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Submit_Button_Test.php new file mode 100644 index 000000000..dd72d4c96 --- /dev/null +++ b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Submit_Button_Test.php @@ -0,0 +1,226 @@ +field = new Signup_Field_Submit_Button(); + } + + /** + * Test get_type returns submit_button. + */ + public function test_get_type(): void { + $this->assertEquals('submit_button', $this->field->get_type()); + } + + /** + * Test is_required returns true. + */ + public function test_is_required(): void { + $this->assertTrue($this->field->is_required()); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->field->get_title(); + $this->assertIsString($title); + $this->assertEquals('Submit Button', $title); + } + + /** + * Test get_description returns string. + */ + public function test_get_description(): void { + $description = $this->field->get_description(); + $this->assertIsString($description); + $this->assertNotEmpty($description); + } + + /** + * Test get_tooltip returns string. + */ + public function test_get_tooltip(): void { + $tooltip = $this->field->get_tooltip(); + $this->assertIsString($tooltip); + $this->assertNotEmpty($tooltip); + } + + /** + * Test get_icon returns dashicon class. + */ + public function test_get_icon(): void { + $icon = $this->field->get_icon(); + $this->assertIsString($icon); + $this->assertStringContainsString('dashicons', $icon); + } + + /** + * Test defaults returns array. + */ + public function test_defaults(): void { + $defaults = $this->field->defaults(); + $this->assertIsArray($defaults); + $this->assertArrayHasKey('enable_go_back_button', $defaults); + $this->assertArrayHasKey('back_button_label', $defaults); + } + + /** + * Test defaults has correct values. + */ + public function test_defaults_values(): void { + $defaults = $this->field->defaults(); + $this->assertFalse($defaults['enable_go_back_button']); + $this->assertIsString($defaults['back_button_label']); + } + + /** + * Test default_fields returns expected fields. + */ + public function test_default_fields(): void { + $fields = $this->field->default_fields(); + $this->assertIsArray($fields); + $this->assertContains('id', $fields); + $this->assertContains('name', $fields); + } + + /** + * Test get_fields returns array. + */ + public function test_get_fields(): void { + $fields = $this->field->get_fields(); + $this->assertIsArray($fields); + $this->assertArrayHasKey('enable_go_back_button', $fields); + $this->assertArrayHasKey('back_button_label', $fields); + } + + /** + * Test get_fields structure. + */ + public function test_get_fields_structure(): void { + $fields = $this->field->get_fields(); + + $this->assertEquals('toggle', $fields['enable_go_back_button']['type']); + $this->assertEquals('text', $fields['back_button_label']['type']); + } + + /** + * Test to_fields_array returns proper structure. + */ + public function test_to_fields_array(): void { + $attributes = [ + 'id' => 'submit_btn', + 'name' => 'Submit', + 'step' => 'checkout', + 'enable_go_back_button' => false, + 'back_button_label' => 'Go Back', + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('submit_btn_errors', $fields); + $this->assertArrayHasKey('submit_btn_group', $fields); + } + + /** + * Test to_fields_array has error field. + */ + public function test_to_fields_array_error_field(): void { + $attributes = [ + 'id' => 'submit_btn', + 'name' => 'Submit', + 'step' => 'checkout', + 'enable_go_back_button' => false, + 'back_button_label' => 'Go Back', + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertEquals('html', $fields['submit_btn_errors']['type']); + } + + /** + * Test to_fields_array has group field. + */ + public function test_to_fields_array_group_field(): void { + $attributes = [ + 'id' => 'submit_btn', + 'name' => 'Submit', + 'step' => 'checkout', + 'enable_go_back_button' => false, + 'back_button_label' => 'Go Back', + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $this->assertEquals('group', $fields['submit_btn_group']['type']); + $this->assertIsArray($fields['submit_btn_group']['fields']); + } + + /** + * Test to_fields_array submit button in group. + */ + public function test_to_fields_array_submit_in_group(): void { + $attributes = [ + 'id' => 'submit_btn', + 'name' => 'Submit', + 'step' => 'checkout', + 'enable_go_back_button' => false, + 'back_button_label' => 'Go Back', + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + $group_fields = $fields['submit_btn_group']['fields']; + $this->assertArrayHasKey('submit_btn', $group_fields); + $this->assertEquals('submit', $group_fields['submit_btn']['type']); + $this->assertEquals('Submit', $group_fields['submit_btn']['name']); + } + + /** + * Test to_fields_array with go back enabled. + */ + public function test_to_fields_array_with_go_back(): void { + $attributes = [ + 'id' => 'submit_btn', + 'name' => 'Submit', + 'step' => 'step_2', + 'enable_go_back_button' => true, + 'back_button_label' => 'Go Back', + ]; + + $this->field->set_attributes($attributes); + $fields = $this->field->to_fields_array($attributes); + + // When go back is enabled, should have clear field + $this->assertArrayHasKey('submit_btn_clear', $fields); + $this->assertEquals('clear', $fields['submit_btn_clear']['type']); + } +} diff --git a/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Text_Test.php b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Text_Test.php new file mode 100644 index 000000000..6505cf874 --- /dev/null +++ b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Text_Test.php @@ -0,0 +1,166 @@ +field = new Signup_Field_Text(); + } + + /** + * Test get_type returns text. + */ + public function test_get_type(): void { + $this->assertEquals('text', $this->field->get_type()); + } + + /** + * Test is_required returns false. + */ + public function test_is_required(): void { + $this->assertFalse($this->field->is_required()); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->field->get_title(); + $this->assertIsString($title); + $this->assertEquals('Text', $title); + } + + /** + * Test get_description returns string. + */ + public function test_get_description(): void { + $description = $this->field->get_description(); + $this->assertIsString($description); + $this->assertNotEmpty($description); + } + + /** + * Test get_tooltip returns string. + */ + public function test_get_tooltip(): void { + $tooltip = $this->field->get_tooltip(); + $this->assertIsString($tooltip); + $this->assertNotEmpty($tooltip); + } + + /** + * Test get_icon returns dashicon class. + */ + public function test_get_icon(): void { + $icon = $this->field->get_icon(); + $this->assertIsString($icon); + $this->assertStringContainsString('dashicons', $icon); + } + + /** + * Test defaults returns array. + */ + public function test_defaults(): void { + $defaults = $this->field->defaults(); + $this->assertIsArray($defaults); + } + + /** + * Test default_fields returns expected fields. + */ + public function test_default_fields(): void { + $fields = $this->field->default_fields(); + $this->assertIsArray($fields); + $this->assertContains('id', $fields); + $this->assertContains('name', $fields); + $this->assertContains('placeholder', $fields); + $this->assertContains('required', $fields); + } + + /** + * Test get_fields returns array. + */ + public function test_get_fields(): void { + $fields = $this->field->get_fields(); + $this->assertIsArray($fields); + } + + /** + * Test to_fields_array returns proper structure. + */ + public function test_to_fields_array(): void { + $attributes = [ + 'id' => 'test_field', + 'name' => 'Test Field', + 'placeholder' => 'Enter text', + 'tooltip' => 'Help text', + 'required' => false, + 'element_classes' => 'custom-class', + 'default_value' => '', + ]; + + // Set attributes on the field first + $this->field->set_attributes($attributes); + + $fields = $this->field->to_fields_array($attributes); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('test_field', $fields); + $this->assertEquals('text', $fields['test_field']['type']); + $this->assertEquals('Test Field', $fields['test_field']['name']); + $this->assertEquals('Enter text', $fields['test_field']['placeholder']); + } + + /** + * Test is_hidden returns false by default. + */ + public function test_is_hidden(): void { + $this->assertFalse($this->field->is_hidden()); + } + + /** + * Test is_site_field returns false. + */ + public function test_is_site_field(): void { + $this->assertFalse($this->field->is_site_field()); + } + + /** + * Test is_user_field returns false. + */ + public function test_is_user_field(): void { + $this->assertFalse($this->field->is_user_field()); + } + + /** + * Test get_field_as_type_option returns array. + */ + public function test_get_field_as_type_option(): void { + $option = $this->field->get_field_as_type_option(); + + $this->assertIsArray($option); + $this->assertArrayHasKey('title', $option); + $this->assertArrayHasKey('desc', $option); + $this->assertArrayHasKey('tooltip', $option); + } +} diff --git a/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Username_Test.php b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Username_Test.php new file mode 100644 index 000000000..da37122c7 --- /dev/null +++ b/tests/WP_Ultimo/Checkout/Signup_Fields/Signup_Field_Username_Test.php @@ -0,0 +1,99 @@ +field = new Signup_Field_Username(); + } + + /** + * Test get_type returns username. + */ + public function test_get_type(): void { + $this->assertEquals('username', $this->field->get_type()); + } + + /** + * Test is_required returns true. + */ + public function test_is_required(): void { + $this->assertTrue($this->field->is_required()); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->field->get_title(); + $this->assertIsString($title); + $this->assertNotEmpty($title); + } + + /** + * Test get_description returns string. + */ + public function test_get_description(): void { + $description = $this->field->get_description(); + $this->assertIsString($description); + $this->assertNotEmpty($description); + } + + /** + * Test get_icon returns dashicon class. + */ + public function test_get_icon(): void { + $icon = $this->field->get_icon(); + $this->assertIsString($icon); + } + + /** + * Test is_user_field returns true. + */ + public function test_is_user_field(): void { + $this->assertTrue($this->field->is_user_field()); + } + + /** + * Test is_site_field returns false. + */ + public function test_is_site_field(): void { + $this->assertFalse($this->field->is_site_field()); + } + + /** + * Test default_fields contains expected fields. + */ + public function test_default_fields(): void { + $fields = $this->field->default_fields(); + $this->assertIsArray($fields); + } + + /** + * Test get_fields returns array. + */ + public function test_get_fields(): void { + $fields = $this->field->get_fields(); + $this->assertIsArray($fields); + } +} diff --git a/tests/WP_Ultimo/Database/Membership_Status_Test.php b/tests/WP_Ultimo/Database/Membership_Status_Test.php new file mode 100644 index 000000000..fb7a16f61 --- /dev/null +++ b/tests/WP_Ultimo/Database/Membership_Status_Test.php @@ -0,0 +1,176 @@ +assertEquals('pending', Membership_Status::PENDING); + $this->assertEquals('active', Membership_Status::ACTIVE); + $this->assertEquals('trialing', Membership_Status::TRIALING); + $this->assertEquals('expired', Membership_Status::EXPIRED); + $this->assertEquals('on-hold', Membership_Status::ON_HOLD); + $this->assertEquals('cancelled', Membership_Status::CANCELLED); + } + + /** + * Test default value is pending. + */ + public function test_default_value(): void { + $status = new Membership_Status(); + $this->assertEquals('pending', $status->get_value()); + } + + /** + * Test get_value with valid status. + */ + public function test_get_value_valid(): void { + $status = new Membership_Status(Membership_Status::ACTIVE); + $this->assertEquals('active', $status->get_value()); + } + + /** + * Test get_value with invalid status returns default. + */ + public function test_get_value_invalid_returns_default(): void { + $status = new Membership_Status('invalid_status'); + $this->assertEquals('pending', $status->get_value()); + } + + /** + * Test is_valid with valid status. + */ + public function test_is_valid_true(): void { + $status = new Membership_Status(); + $this->assertTrue($status->is_valid(Membership_Status::PENDING)); + $this->assertTrue($status->is_valid(Membership_Status::ACTIVE)); + $this->assertTrue($status->is_valid(Membership_Status::TRIALING)); + $this->assertTrue($status->is_valid(Membership_Status::EXPIRED)); + $this->assertTrue($status->is_valid(Membership_Status::ON_HOLD)); + $this->assertTrue($status->is_valid(Membership_Status::CANCELLED)); + } + + /** + * Test is_valid with invalid status. + */ + public function test_is_valid_false(): void { + $status = new Membership_Status(); + $this->assertFalse($status->is_valid('invalid')); + $this->assertFalse($status->is_valid('')); + $this->assertFalse($status->is_valid('ACTIVE')); // Case sensitive + } + + /** + * Test get_label returns correct label. + */ + public function test_get_label(): void { + $pending = new Membership_Status(Membership_Status::PENDING); + $this->assertEquals('Pending', $pending->get_label()); + + $active = new Membership_Status(Membership_Status::ACTIVE); + $this->assertEquals('Active', $active->get_label()); + + $trialing = new Membership_Status(Membership_Status::TRIALING); + $this->assertEquals('Trialing', $trialing->get_label()); + + $onhold = new Membership_Status(Membership_Status::ON_HOLD); + $this->assertEquals('On Hold', $onhold->get_label()); + } + + /** + * Test get_classes returns CSS classes. + */ + public function test_get_classes(): void { + $pending = new Membership_Status(Membership_Status::PENDING); + $this->assertStringContainsString('wu-bg-gray-200', $pending->get_classes()); + + $active = new Membership_Status(Membership_Status::ACTIVE); + $this->assertStringContainsString('wu-bg-green-200', $active->get_classes()); + + $cancelled = new Membership_Status(Membership_Status::CANCELLED); + $this->assertStringContainsString('wu-bg-red-200', $cancelled->get_classes()); + } + + /** + * Test get_options returns all status options. + */ + public function test_get_options(): void { + $options = Membership_Status::get_options(); + + $this->assertIsArray($options); + $this->assertContains(Membership_Status::PENDING, $options); + $this->assertContains(Membership_Status::ACTIVE, $options); + $this->assertContains(Membership_Status::TRIALING, $options); + } + + /** + * Test get_allowed_list returns array. + */ + public function test_get_allowed_list_array(): void { + $list = Membership_Status::get_allowed_list(); + + $this->assertIsArray($list); + $this->assertContains(Membership_Status::ACTIVE, $list); + } + + /** + * Test get_allowed_list returns string. + */ + public function test_get_allowed_list_string(): void { + $list = Membership_Status::get_allowed_list(true); + + $this->assertIsString($list); + $this->assertStringContainsString('active', $list); + $this->assertStringContainsString('pending', $list); + } + + /** + * Test to_array returns labels. + */ + public function test_to_array(): void { + $labels = Membership_Status::to_array(); + + $this->assertIsArray($labels); + $this->assertArrayHasKey(Membership_Status::PENDING, $labels); + $this->assertArrayHasKey(Membership_Status::ACTIVE, $labels); + $this->assertEquals('Active', $labels[Membership_Status::ACTIVE]); + } + + /** + * Test __toString returns value. + */ + public function test_to_string(): void { + $status = new Membership_Status(Membership_Status::ACTIVE); + $this->assertEquals('active', (string) $status); + } + + /** + * Test static call returns constant value. + */ + public function test_static_call(): void { + $this->assertEquals('pending', Membership_Status::PENDING()); + $this->assertEquals('active', Membership_Status::ACTIVE()); + } + + /** + * Test get_hook_name returns correct hook. + */ + public function test_get_hook_name(): void { + $hook = Membership_Status::get_hook_name(); + $this->assertEquals('membership_status', $hook); + } +} diff --git a/tests/WP_Ultimo/Database/Payment_Status_Test.php b/tests/WP_Ultimo/Database/Payment_Status_Test.php new file mode 100644 index 000000000..d9163b235 --- /dev/null +++ b/tests/WP_Ultimo/Database/Payment_Status_Test.php @@ -0,0 +1,184 @@ +assertEquals('pending', Payment_Status::PENDING); + $this->assertEquals('completed', Payment_Status::COMPLETED); + $this->assertEquals('refunded', Payment_Status::REFUND); + $this->assertEquals('partially-refunded', Payment_Status::PARTIAL_REFUND); + $this->assertEquals('partially-paid', Payment_Status::PARTIAL); + $this->assertEquals('failed', Payment_Status::FAILED); + $this->assertEquals('cancelled', Payment_Status::CANCELLED); + $this->assertEquals('draft', Payment_Status::DRAFT); + } + + /** + * Test default value is pending. + */ + public function test_default_value(): void { + $status = new Payment_Status(); + $this->assertEquals('pending', $status->get_value()); + } + + /** + * Test get_value with valid status. + */ + public function test_get_value_valid(): void { + $status = new Payment_Status(Payment_Status::COMPLETED); + $this->assertEquals('completed', $status->get_value()); + } + + /** + * Test get_value with invalid status returns default. + */ + public function test_get_value_invalid_returns_default(): void { + $status = new Payment_Status('invalid_status'); + $this->assertEquals('pending', $status->get_value()); + } + + /** + * Test is_valid with valid status. + */ + public function test_is_valid_true(): void { + $status = new Payment_Status(); + $this->assertTrue($status->is_valid(Payment_Status::PENDING)); + $this->assertTrue($status->is_valid(Payment_Status::COMPLETED)); + $this->assertTrue($status->is_valid(Payment_Status::REFUND)); + } + + /** + * Test is_valid with invalid status. + */ + public function test_is_valid_false(): void { + $status = new Payment_Status(); + $this->assertFalse($status->is_valid('invalid')); + $this->assertFalse($status->is_valid('')); + $this->assertFalse($status->is_valid('PENDING')); // Case sensitive + } + + /** + * Test get_label returns correct label. + */ + public function test_get_label(): void { + $pending = new Payment_Status(Payment_Status::PENDING); + $this->assertEquals('Pending', $pending->get_label()); + + $completed = new Payment_Status(Payment_Status::COMPLETED); + $this->assertEquals('Completed', $completed->get_label()); + + $refunded = new Payment_Status(Payment_Status::REFUND); + $this->assertEquals('Refunded', $refunded->get_label()); + } + + /** + * Test get_classes returns CSS classes. + */ + public function test_get_classes(): void { + $pending = new Payment_Status(Payment_Status::PENDING); + $this->assertStringContainsString('wu-bg-gray-200', $pending->get_classes()); + + $completed = new Payment_Status(Payment_Status::COMPLETED); + $this->assertStringContainsString('wu-bg-green-200', $completed->get_classes()); + + $failed = new Payment_Status(Payment_Status::FAILED); + $this->assertStringContainsString('wu-bg-red-200', $failed->get_classes()); + } + + /** + * Test get_icon_classes returns icon classes. + */ + public function test_get_icon_classes(): void { + $pending = new Payment_Status(Payment_Status::PENDING); + $this->assertStringContainsString('dashicons-wu-clock', $pending->get_icon_classes()); + + $completed = new Payment_Status(Payment_Status::COMPLETED); + $this->assertStringContainsString('dashicons-wu-check', $completed->get_icon_classes()); + } + + /** + * Test get_options returns all status options. + */ + public function test_get_options(): void { + $options = Payment_Status::get_options(); + + $this->assertIsArray($options); + $this->assertContains(Payment_Status::PENDING, $options); + $this->assertContains(Payment_Status::COMPLETED, $options); + $this->assertContains(Payment_Status::FAILED, $options); + } + + /** + * Test get_allowed_list returns array. + */ + public function test_get_allowed_list_array(): void { + $list = Payment_Status::get_allowed_list(); + + $this->assertIsArray($list); + $this->assertContains(Payment_Status::PENDING, $list); + } + + /** + * Test get_allowed_list returns string. + */ + public function test_get_allowed_list_string(): void { + $list = Payment_Status::get_allowed_list(true); + + $this->assertIsString($list); + $this->assertStringContainsString('pending', $list); + $this->assertStringContainsString('completed', $list); + } + + /** + * Test to_array returns labels. + */ + public function test_to_array(): void { + $labels = Payment_Status::to_array(); + + $this->assertIsArray($labels); + $this->assertArrayHasKey(Payment_Status::PENDING, $labels); + // to_array returns labels array which has status values as keys + $this->assertNotEmpty($labels); + $this->assertEquals('Pending', $labels[Payment_Status::PENDING]); + } + + /** + * Test __toString returns value. + */ + public function test_to_string(): void { + $status = new Payment_Status(Payment_Status::COMPLETED); + $this->assertEquals('completed', (string) $status); + } + + /** + * Test static call returns constant value. + */ + public function test_static_call(): void { + $this->assertEquals('pending', Payment_Status::PENDING()); + $this->assertEquals('completed', Payment_Status::COMPLETED()); + } + + /** + * Test get_hook_name returns correct hook. + */ + public function test_get_hook_name(): void { + $hook = Payment_Status::get_hook_name(); + $this->assertEquals('payment_status', $hook); + } +} diff --git a/tests/WP_Ultimo/Database/Product_Type_Test.php b/tests/WP_Ultimo/Database/Product_Type_Test.php new file mode 100644 index 000000000..fdf1ecdb6 --- /dev/null +++ b/tests/WP_Ultimo/Database/Product_Type_Test.php @@ -0,0 +1,159 @@ +assertEquals('plan', Product_Type::PLAN); + $this->assertEquals('package', Product_Type::PACKAGE); + $this->assertEquals('service', Product_Type::SERVICE); + } + + /** + * Test default value is plan. + */ + public function test_default_value(): void { + $type = new Product_Type(); + $this->assertEquals('plan', $type->get_value()); + } + + /** + * Test get_value with valid type. + */ + public function test_get_value_valid(): void { + $type = new Product_Type(Product_Type::PACKAGE); + $this->assertEquals('package', $type->get_value()); + } + + /** + * Test get_value with invalid type returns default. + */ + public function test_get_value_invalid_returns_default(): void { + $type = new Product_Type('invalid_type'); + $this->assertEquals('plan', $type->get_value()); + } + + /** + * Test is_valid with valid types. + */ + public function test_is_valid_true(): void { + $type = new Product_Type(); + $this->assertTrue($type->is_valid(Product_Type::PLAN)); + $this->assertTrue($type->is_valid(Product_Type::PACKAGE)); + $this->assertTrue($type->is_valid(Product_Type::SERVICE)); + } + + /** + * Test is_valid with invalid types. + */ + public function test_is_valid_false(): void { + $type = new Product_Type(); + $this->assertFalse($type->is_valid('invalid')); + $this->assertFalse($type->is_valid('')); + $this->assertFalse($type->is_valid('PLAN')); // Case sensitive + } + + /** + * Test get_label returns correct label. + */ + public function test_get_label(): void { + $plan = new Product_Type(Product_Type::PLAN); + $this->assertEquals('Plan', $plan->get_label()); + + $package = new Product_Type(Product_Type::PACKAGE); + $this->assertEquals('Package', $package->get_label()); + + $service = new Product_Type(Product_Type::SERVICE); + $this->assertEquals('Service', $service->get_label()); + } + + /** + * Test get_classes returns CSS classes. + */ + public function test_get_classes(): void { + $plan = new Product_Type(Product_Type::PLAN); + $this->assertStringContainsString('wu-bg-green-200', $plan->get_classes()); + + $package = new Product_Type(Product_Type::PACKAGE); + $this->assertStringContainsString('wu-bg-gray-200', $package->get_classes()); + + $service = new Product_Type(Product_Type::SERVICE); + $this->assertStringContainsString('wu-bg-yellow-200', $service->get_classes()); + } + + /** + * Test get_options returns all type options. + */ + public function test_get_options(): void { + $options = Product_Type::get_options(); + + $this->assertIsArray($options); + $this->assertContains(Product_Type::PLAN, $options); + $this->assertContains(Product_Type::PACKAGE, $options); + $this->assertContains(Product_Type::SERVICE, $options); + } + + /** + * Test get_allowed_list returns array. + */ + public function test_get_allowed_list_array(): void { + $list = Product_Type::get_allowed_list(); + + $this->assertIsArray($list); + $this->assertContains(Product_Type::PLAN, $list); + } + + /** + * Test get_allowed_list returns string. + */ + public function test_get_allowed_list_string(): void { + $list = Product_Type::get_allowed_list(true); + + $this->assertIsString($list); + $this->assertStringContainsString('plan', $list); + $this->assertStringContainsString('package', $list); + } + + /** + * Test to_array returns labels. + */ + public function test_to_array(): void { + $labels = Product_Type::to_array(); + + $this->assertIsArray($labels); + $this->assertNotEmpty($labels); + // Check that we have at least one label + $this->assertGreaterThan(0, count($labels)); + } + + /** + * Test __toString returns value. + */ + public function test_to_string(): void { + $type = new Product_Type(Product_Type::PACKAGE); + $this->assertEquals('package', (string) $type); + } + + /** + * Test static call returns constant value. + */ + public function test_static_call(): void { + $this->assertEquals('plan', Product_Type::PLAN()); + $this->assertEquals('package', Product_Type::PACKAGE()); + } +} diff --git a/tests/WP_Ultimo/Database/Site_Type_Test.php b/tests/WP_Ultimo/Database/Site_Type_Test.php new file mode 100644 index 000000000..765bafe75 --- /dev/null +++ b/tests/WP_Ultimo/Database/Site_Type_Test.php @@ -0,0 +1,165 @@ +assertEquals('default', Site_Type::REGULAR); + $this->assertEquals('site_template', Site_Type::SITE_TEMPLATE); + $this->assertEquals('customer_owned', Site_Type::CUSTOMER_OWNED); + $this->assertEquals('pending', Site_Type::PENDING); + $this->assertEquals('external', Site_Type::EXTERNAL); + $this->assertEquals('main', Site_Type::MAIN); + } + + /** + * Test default value is default. + */ + public function test_default_value(): void { + $type = new Site_Type(); + $this->assertEquals('default', $type->get_value()); + } + + /** + * Test get_value with valid type. + */ + public function test_get_value_valid(): void { + $type = new Site_Type(Site_Type::SITE_TEMPLATE); + $this->assertEquals('site_template', $type->get_value()); + } + + /** + * Test get_value with invalid type returns default. + */ + public function test_get_value_invalid_returns_default(): void { + $type = new Site_Type('invalid_type'); + $this->assertEquals('default', $type->get_value()); + } + + /** + * Test is_valid with valid types. + */ + public function test_is_valid_true(): void { + $type = new Site_Type(); + $this->assertTrue($type->is_valid(Site_Type::REGULAR)); + $this->assertTrue($type->is_valid(Site_Type::SITE_TEMPLATE)); + $this->assertTrue($type->is_valid(Site_Type::CUSTOMER_OWNED)); + $this->assertTrue($type->is_valid(Site_Type::PENDING)); + $this->assertTrue($type->is_valid(Site_Type::EXTERNAL)); + $this->assertTrue($type->is_valid(Site_Type::MAIN)); + } + + /** + * Test is_valid with invalid types. + */ + public function test_is_valid_false(): void { + $type = new Site_Type(); + $this->assertFalse($type->is_valid('invalid')); + $this->assertFalse($type->is_valid('')); + $this->assertFalse($type->is_valid('DEFAULT')); // Case sensitive + } + + /** + * Test get_label returns correct label. + */ + public function test_get_label(): void { + $regular = new Site_Type(Site_Type::REGULAR); + $this->assertEquals('Regular Site', $regular->get_label()); + + $template = new Site_Type(Site_Type::SITE_TEMPLATE); + $this->assertEquals('Site Template', $template->get_label()); + + $customer = new Site_Type(Site_Type::CUSTOMER_OWNED); + $this->assertEquals('Customer-Owned', $customer->get_label()); + } + + /** + * Test get_classes returns CSS classes. + */ + public function test_get_classes(): void { + $regular = new Site_Type(Site_Type::REGULAR); + $this->assertStringContainsString('wu-bg-gray-700', $regular->get_classes()); + + $template = new Site_Type(Site_Type::SITE_TEMPLATE); + $this->assertStringContainsString('wu-bg-yellow-200', $template->get_classes()); + + $customer = new Site_Type(Site_Type::CUSTOMER_OWNED); + $this->assertStringContainsString('wu-bg-green-200', $customer->get_classes()); + } + + /** + * Test get_options returns all type options. + */ + public function test_get_options(): void { + $options = Site_Type::get_options(); + + $this->assertIsArray($options); + $this->assertContains(Site_Type::REGULAR, $options); + $this->assertContains(Site_Type::SITE_TEMPLATE, $options); + $this->assertContains(Site_Type::CUSTOMER_OWNED, $options); + } + + /** + * Test get_allowed_list returns array. + */ + public function test_get_allowed_list_array(): void { + $list = Site_Type::get_allowed_list(); + + $this->assertIsArray($list); + $this->assertContains(Site_Type::REGULAR, $list); + } + + /** + * Test get_allowed_list returns string. + */ + public function test_get_allowed_list_string(): void { + $list = Site_Type::get_allowed_list(true); + + $this->assertIsString($list); + $this->assertStringContainsString('default', $list); + $this->assertStringContainsString('site_template', $list); + } + + /** + * Test to_array returns labels. + */ + public function test_to_array(): void { + $labels = Site_Type::to_array(); + + $this->assertIsArray($labels); + $this->assertNotEmpty($labels); + // Check that we have at least one label + $this->assertGreaterThan(0, count($labels)); + } + + /** + * Test __toString returns value. + */ + public function test_to_string(): void { + $type = new Site_Type(Site_Type::SITE_TEMPLATE); + $this->assertEquals('site_template', (string) $type); + } + + /** + * Test static call returns constant value. + */ + public function test_static_call(): void { + $this->assertEquals('default', Site_Type::REGULAR()); + $this->assertEquals('site_template', Site_Type::SITE_TEMPLATE()); + } +} diff --git a/tests/WP_Ultimo/Functions/Array_Helpers_Test.php b/tests/WP_Ultimo/Functions/Array_Helpers_Test.php new file mode 100644 index 000000000..3aa752ed1 --- /dev/null +++ b/tests/WP_Ultimo/Functions/Array_Helpers_Test.php @@ -0,0 +1,319 @@ + [ + 'b' => 'value1', + 'c' => 'value2', + ], + ]; + + $result = wu_array_flatten($array); + + $this->assertContains('value1', $result); + $this->assertContains('value2', $result); + } + + /** + * Test wu_array_flatten with deeply nested array. + */ + public function test_array_flatten_deeply_nested(): void { + $array = [ + 'level1' => [ + 'level2' => [ + 'level3' => 'deep_value', + ], + ], + ]; + + $result = wu_array_flatten($array); + + $this->assertContains('deep_value', $result); + } + + /** + * Test wu_array_flatten with indexes. + */ + public function test_array_flatten_with_indexes(): void { + $array = [ + 'key1' => 'value1', + 'key2' => 'value2', + ]; + + $result = wu_array_flatten($array, true); + + $this->assertContains('key1', $result); + $this->assertContains('value1', $result); + $this->assertContains('key2', $result); + $this->assertContains('value2', $result); + } + + /** + * Test wu_array_flatten with empty array. + */ + public function test_array_flatten_empty(): void { + $result = wu_array_flatten([]); + + $this->assertEmpty($result); + $this->assertIsArray($result); + } + + /** + * Test wu_array_merge_recursive_distinct basic merge. + */ + public function test_array_merge_recursive_distinct_basic(): void { + $array1 = ['key' => 'value1']; + $array2 = ['key' => 'value2']; + + $result = wu_array_merge_recursive_distinct($array1, $array2); + + $this->assertEquals('value2', $result['key']); // Second array wins + } + + /** + * Test wu_array_merge_recursive_distinct adds new keys. + */ + public function test_array_merge_recursive_distinct_adds_keys(): void { + $array1 = ['key1' => 'value1']; + $array2 = ['key2' => 'value2']; + + $result = wu_array_merge_recursive_distinct($array1, $array2); + + $this->assertEquals('value1', $result['key1']); + $this->assertEquals('value2', $result['key2']); + } + + /** + * Test wu_array_merge_recursive_distinct with nested arrays. + */ + public function test_array_merge_recursive_distinct_nested(): void { + $array1 = [ + 'nested' => [ + 'a' => 1, + 'b' => 2, + ], + ]; + $array2 = [ + 'nested' => [ + 'b' => 3, + 'c' => 4, + ], + ]; + + $result = wu_array_merge_recursive_distinct($array1, $array2); + + $this->assertEquals(1, $result['nested']['a']); + $this->assertEquals(5, $result['nested']['b']); // 2 + 3 = 5 (summed) + $this->assertEquals(4, $result['nested']['c']); + } + + /** + * Test wu_array_merge_recursive_distinct sums numeric values. + */ + public function test_array_merge_recursive_distinct_sums_numeric(): void { + $array1 = ['count' => 10]; + $array2 = ['count' => 5]; + + $result = wu_array_merge_recursive_distinct($array1, $array2, true); + + $this->assertEquals(15, $result['count']); // 10 + 5 = 15 + } + + /** + * Test wu_array_merge_recursive_distinct without summing. + */ + public function test_array_merge_recursive_distinct_no_sum(): void { + $array1 = ['count' => 10]; + $array2 = ['count' => 5]; + + $result = wu_array_merge_recursive_distinct($array1, $array2, false); + + $this->assertEquals(5, $result['count']); // Replaced, not summed + } + + /** + * Test wu_array_recursive_diff basic diff. + */ + public function test_array_recursive_diff_basic(): void { + $array1 = ['a' => 1, 'b' => 2, 'c' => 3]; + $array2 = ['a' => 1, 'b' => 2, 'c' => 3]; + + $result = wu_array_recursive_diff($array1, $array2); + + $this->assertEmpty($result); // Arrays are identical + } + + /** + * Test wu_array_recursive_diff finds differences. + */ + public function test_array_recursive_diff_finds_differences(): void { + $array1 = ['a' => 1, 'b' => 2]; + $array2 = ['a' => 1, 'b' => 3]; + + $result = wu_array_recursive_diff($array1, $array2); + + $this->assertArrayHasKey('b', $result); + $this->assertEquals(2, $result['b']); + } + + /** + * Test wu_array_recursive_diff with missing keys. + */ + public function test_array_recursive_diff_missing_keys(): void { + $array1 = ['a' => 1, 'b' => 2, 'c' => 3]; + $array2 = ['a' => 1]; + + $result = wu_array_recursive_diff($array1, $array2); + + $this->assertArrayHasKey('b', $result); + $this->assertArrayHasKey('c', $result); + } + + /** + * Test wu_array_recursive_diff with nested arrays. + */ + public function test_array_recursive_diff_nested(): void { + $array1 = ['nested' => ['a' => 1, 'b' => 2]]; + $array2 = ['nested' => ['a' => 1, 'b' => 3]]; + + $result = wu_array_recursive_diff($array1, $array2); + + $this->assertArrayHasKey('nested', $result); + $this->assertArrayHasKey('b', $result['nested']); + } + + /** + * Test wu_array_map_keys function. + */ + public function test_array_map_keys(): void { + $array = [ + 'first_key' => 'value1', + 'second_key' => 'value2', + ]; + + $result = wu_array_map_keys('strtoupper', $array); + + $this->assertArrayHasKey('FIRST_KEY', $result); + $this->assertArrayHasKey('SECOND_KEY', $result); + $this->assertEquals('value1', $result['FIRST_KEY']); + $this->assertEquals('value2', $result['SECOND_KEY']); + } + + /** + * Test wu_key_map_to_array basic conversion. + */ + public function test_key_map_to_array_basic(): void { + $assoc_array = [ + 'key1' => 'value1', + 'key2' => 'value2', + ]; + + $result = wu_key_map_to_array($assoc_array); + + $this->assertCount(2, $result); + $this->assertEquals('key1', $result[0]['id']); + $this->assertEquals('value1', $result[0]['value']); + $this->assertEquals('key2', $result[1]['id']); + $this->assertEquals('value2', $result[1]['value']); + } + + /** + * Test wu_key_map_to_array with custom key names. + */ + public function test_key_map_to_array_custom_names(): void { + $assoc_array = ['foo' => 'bar']; + + $result = wu_key_map_to_array($assoc_array, 'name', 'data'); + + $this->assertEquals('foo', $result[0]['name']); + $this->assertEquals('bar', $result[0]['data']); + } + + /** + * Test wu_key_map_to_array with empty array. + */ + public function test_key_map_to_array_empty(): void { + $result = wu_key_map_to_array([]); + + $this->assertEmpty($result); + $this->assertIsArray($result); + } + + /** + * Test wu_array_find_first_by finds first match. + */ + public function test_array_find_first_by(): void { + $array = [ + ['id' => 1, 'name' => 'Alice'], + ['id' => 2, 'name' => 'Bob'], + ['id' => 3, 'name' => 'Alice'], + ]; + + $result = wu_array_find_first_by($array, 'name', 'Alice'); + + $this->assertIsArray($result); + $this->assertEquals(1, $result['id']); + } + + /** + * Test wu_array_find_last_by finds last match. + */ + public function test_array_find_last_by(): void { + $array = [ + ['id' => 1, 'name' => 'Alice'], + ['id' => 2, 'name' => 'Bob'], + ['id' => 3, 'name' => 'Alice'], + ]; + + $result = wu_array_find_last_by($array, 'name', 'Alice'); + + $this->assertIsArray($result); + $this->assertEquals(3, $result['id']); + } + + /** + * Test wu_array_find_all_by finds all matches. + */ + public function test_array_find_all_by(): void { + $array = [ + ['id' => 1, 'name' => 'Alice'], + ['id' => 2, 'name' => 'Bob'], + ['id' => 3, 'name' => 'Alice'], + ]; + + $result = wu_array_find_all_by($array, 'name', 'Alice'); + + $this->assertCount(2, $result); + } + + /** + * Test wu_array_find_first_by with no match. + */ + public function test_array_find_first_by_no_match(): void { + $array = [ + ['id' => 1, 'name' => 'Alice'], + ['id' => 2, 'name' => 'Bob'], + ]; + + $result = wu_array_find_first_by($array, 'name', 'Charlie'); + + $this->assertFalse($result); + } +} diff --git a/tests/WP_Ultimo/Functions/Checkout_Functions_Test.php b/tests/WP_Ultimo/Functions/Checkout_Functions_Test.php new file mode 100644 index 000000000..828ea19f1 --- /dev/null +++ b/tests/WP_Ultimo/Functions/Checkout_Functions_Test.php @@ -0,0 +1,187 @@ +assertInstanceOf(\WP_Error::class, $errors); + } + + /** + * Test wu_errors returns same instance. + */ + public function test_errors_singleton(): void { + $errors1 = wu_errors(); + $errors2 = wu_errors(); + + $this->assertSame($errors1, $errors2); + } + + /** + * Test wu_stripe_generate_idempotency_key generates string. + */ + public function test_stripe_generate_idempotency_key(): void { + $key = wu_stripe_generate_idempotency_key([ + 'customer_id' => 1, + 'amount' => 100, + ], 'new'); + + $this->assertIsString($key); + $this->assertNotEmpty($key); + } + + /** + * Test wu_stripe_generate_idempotency_key with different args. + */ + public function test_stripe_generate_idempotency_key_different_args(): void { + $key1 = wu_stripe_generate_idempotency_key([ + 'customer_id' => 1, + 'amount' => 100, + ], 'new'); + + $key2 = wu_stripe_generate_idempotency_key([ + 'customer_id' => 2, + 'amount' => 200, + ], 'new'); + + // Different args should produce different keys + $this->assertNotEquals($key1, $key2); + } + + /** + * Test wu_get_days_in_cycle for day. + */ + public function test_get_days_in_cycle_day(): void { + $days = wu_get_days_in_cycle('day', 5); + + $this->assertEquals(5, $days); + } + + /** + * Test wu_get_days_in_cycle for week. + */ + public function test_get_days_in_cycle_week(): void { + $days = wu_get_days_in_cycle('week', 2); + + $this->assertEquals(14, $days); + } + + /** + * Test wu_get_days_in_cycle for month. + */ + public function test_get_days_in_cycle_month(): void { + $days = wu_get_days_in_cycle('month', 1); + + // 30.4375 days per month (avg) + $this->assertEquals(30.4375, $days); + } + + /** + * Test wu_get_days_in_cycle for year. + */ + public function test_get_days_in_cycle_year(): void { + $days = wu_get_days_in_cycle('year', 1); + + // 365.25 days per year (accounting for leap years) + $this->assertEquals(365.25, $days); + } + + /** + * Test wu_get_days_in_cycle for unknown unit. + */ + public function test_get_days_in_cycle_unknown(): void { + $days = wu_get_days_in_cycle('invalid', 1); + + $this->assertEquals(0, $days); + } + + /** + * Test wu_get_days_in_cycle for 3 months. + */ + public function test_get_days_in_cycle_three_months(): void { + $days = wu_get_days_in_cycle('month', 3); + + $this->assertEquals(91.3125, $days); + } + + /** + * Test wu_multiple_memberships_enabled returns boolean. + */ + public function test_multiple_memberships_enabled_returns_bool(): void { + $enabled = wu_multiple_memberships_enabled(); + + $this->assertIsBool($enabled); + } + + /** + * Test wu_get_registration_url returns string. + */ + public function test_get_registration_url_returns_string(): void { + $url = wu_get_registration_url(); + + $this->assertIsString($url); + } + + /** + * Test wu_get_registration_url with path. + */ + public function test_get_registration_url_with_path(): void { + $url = wu_get_registration_url('/custom-path'); + + $this->assertIsString($url); + // URL may be '#no-registration-url' if no checkout form is set up + } + + /** + * Test wu_get_login_url returns string. + */ + public function test_get_login_url_returns_string(): void { + $url = wu_get_login_url(); + + $this->assertIsString($url); + } + + /** + * Test wu_get_login_url with path. + */ + public function test_get_login_url_with_path(): void { + $url = wu_get_login_url('/custom-path'); + + $this->assertIsString($url); + } + + /** + * Test wu_create_checkout_fields returns array. + */ + public function test_create_checkout_fields_returns_array(): void { + $fields = wu_create_checkout_fields([]); + + $this->assertIsArray($fields); + } + + /** + * Test wu_create_checkout_fields with simple fields. + */ + public function test_create_checkout_fields_with_simple_fields(): void { + // Just test with empty array as complex fields need more setup + $fields = wu_create_checkout_fields([]); + + $this->assertIsArray($fields); + } +} diff --git a/tests/WP_Ultimo/Functions/Color_Helpers_Test.php b/tests/WP_Ultimo/Functions/Color_Helpers_Test.php new file mode 100644 index 000000000..e8dbd98f1 --- /dev/null +++ b/tests/WP_Ultimo/Functions/Color_Helpers_Test.php @@ -0,0 +1,114 @@ +assertInstanceOf(Color::class, $color); + } + + /** + * Test wu_color with valid hex colors. + */ + public function test_color_with_valid_hex(): void { + $black = wu_color('#000000'); + $this->assertEquals('000000', $black->getHex()); + + $white = wu_color('#ffffff'); + $this->assertEquals('ffffff', $white->getHex()); + + $red = wu_color('#ff0000'); + $this->assertEquals('ff0000', $red->getHex()); + } + + /** + * Test wu_color with shorthand hex. + */ + public function test_color_with_shorthand_hex(): void { + $color = wu_color('#fff'); + $this->assertInstanceOf(Color::class, $color); + } + + /** + * Test wu_color with invalid hex returns default. + */ + public function test_color_with_invalid_hex(): void { + $color = wu_color('invalid'); + $this->assertInstanceOf(Color::class, $color); + // Should return the fallback color #f9f9f9 + $this->assertEquals('f9f9f9', $color->getHex()); + } + + /** + * Test wu_color can manipulate colors. + */ + public function test_color_manipulation(): void { + $color = wu_color('#336699'); + + // Test darken + $darker = $color->darken(10); + $this->assertNotEquals($color->getHex(), $darker); + + // Test lighten + $lighter = $color->lighten(10); + $this->assertNotEquals($color->getHex(), $lighter); + } + + /** + * Test wu_get_random_color returns valid color class. + */ + public function test_get_random_color_returns_string(): void { + $color = wu_get_random_color(0); + $this->assertIsString($color); + $this->assertStringContainsString('wu-bg-', $color); + } + + /** + * Test wu_get_random_color with different indices. + */ + public function test_get_random_color_with_indices(): void { + $color0 = wu_get_random_color(0); + $color1 = wu_get_random_color(1); + $color2 = wu_get_random_color(2); + + $this->assertEquals('wu-bg-red-500', $color0); + $this->assertEquals('wu-bg-green-500', $color1); + $this->assertEquals('wu-bg-blue-500', $color2); + } + + /** + * Test wu_get_random_color with out of bounds index. + */ + public function test_get_random_color_out_of_bounds(): void { + $color = wu_get_random_color(999); + $this->assertIsString($color); + $this->assertStringContainsString('wu-bg-', $color); + // Should return a random color when index is out of bounds + $this->assertMatchesRegularExpression('/wu-bg-(red|green|blue|yellow|orange|purple|pink)-500/', $color); + } + + /** + * Test wu_get_random_color with negative index. + */ + public function test_get_random_color_negative_index(): void { + $color = wu_get_random_color(-1); + $this->assertIsString($color); + $this->assertStringContainsString('wu-bg-', $color); + } +} diff --git a/tests/WP_Ultimo/Functions/Country_Functions_Test.php b/tests/WP_Ultimo/Functions/Country_Functions_Test.php new file mode 100644 index 000000000..683f934f9 --- /dev/null +++ b/tests/WP_Ultimo/Functions/Country_Functions_Test.php @@ -0,0 +1,175 @@ +assertIsArray($countries); + $this->assertNotEmpty($countries); + } + + /** + * Test wu_get_countries contains common countries. + */ + public function test_get_countries_contains_common(): void { + $countries = wu_get_countries(); + + $this->assertArrayHasKey('US', $countries); + $this->assertArrayHasKey('GB', $countries); + $this->assertArrayHasKey('CA', $countries); + $this->assertArrayHasKey('BR', $countries); + $this->assertArrayHasKey('DE', $countries); + } + + /** + * Test wu_get_countries_as_options returns array. + */ + public function test_get_countries_as_options_returns_array(): void { + $options = wu_get_countries_as_options(); + + $this->assertIsArray($options); + $this->assertNotEmpty($options); + } + + /** + * Test wu_get_country returns object with valid code. + */ + public function test_get_country_valid_code(): void { + $country = wu_get_country('US'); + + $this->assertIsObject($country); + // Check that it has the expected properties + $this->assertTrue(property_exists($country, 'attributes') || method_exists($country, 'get_country_code')); + } + + /** + * Test wu_get_country returns object for invalid code. + */ + public function test_get_country_invalid_code(): void { + $country = wu_get_country('XX', 'Unknown', ['fallback_key' => 'value']); + + $this->assertIsObject($country); + } + + /** + * Test wu_get_country_name returns correct name. + */ + public function test_get_country_name_us(): void { + $name = wu_get_country_name('US'); + + $this->assertIsString($name); + $this->assertEquals('United States (US)', $name); + } + + /** + * Test wu_get_country_name returns correct name for GB. + */ + public function test_get_country_name_gb(): void { + $name = wu_get_country_name('GB'); + + $this->assertIsString($name); + $this->assertEquals('United Kingdom (UK)', $name); + } + + /** + * Test wu_get_country_name returns correct name for BR. + */ + public function test_get_country_name_br(): void { + $name = wu_get_country_name('BR'); + + $this->assertIsString($name); + $this->assertEquals('Brazil', $name); + } + + /** + * Test wu_get_country_name returns something for invalid code. + */ + public function test_get_country_name_invalid(): void { + $name = wu_get_country_name('XX'); + + // Function may return the code itself or empty + $this->assertIsString($name); + } + + /** + * Test wu_get_country_states returns array. + */ + public function test_get_country_states_returns_array(): void { + $states = wu_get_country_states('US'); + + $this->assertIsArray($states); + } + + /** + * Test wu_get_country_states contains states for US. + */ + public function test_get_country_states_us(): void { + $states = wu_get_country_states('US'); + + $this->assertIsArray($states); + $this->assertNotEmpty($states); + } + + /** + * Test wu_get_country_states empty for country without states. + */ + public function test_get_country_states_empty_country(): void { + $states = wu_get_country_states('XX'); + + $this->assertIsArray($states); + // May be empty for invalid or small countries + } + + /** + * Test wu_get_country_cities returns array. + */ + public function test_get_country_cities_returns_array(): void { + $cities = wu_get_country_cities('US', ['CA']); + + $this->assertIsArray($cities); + } + + /** + * Test wu_get_countries_of_customers returns array. + */ + public function test_get_countries_of_customers_returns_array(): void { + $countries = wu_get_countries_of_customers(); + + $this->assertIsArray($countries); + } + + /** + * Test wu_get_countries_of_customers with count limit. + */ + public function test_get_countries_of_customers_with_limit(): void { + $countries = wu_get_countries_of_customers(5); + + $this->assertIsArray($countries); + $this->assertLessThanOrEqual(5, count($countries)); + } + + /** + * Test wu_get_states_of_customers returns array. + */ + public function test_get_states_of_customers_returns_array(): void { + $states = wu_get_states_of_customers('US'); + + $this->assertIsArray($states); + } +} diff --git a/tests/WP_Ultimo/Functions/Currency_Functions_Test.php b/tests/WP_Ultimo/Functions/Currency_Functions_Test.php new file mode 100644 index 000000000..f39204f67 --- /dev/null +++ b/tests/WP_Ultimo/Functions/Currency_Functions_Test.php @@ -0,0 +1,194 @@ +assertIsArray($currencies); + $this->assertNotEmpty($currencies); + } + + /** + * Test wu_get_currencies contains common currencies. + */ + public function test_get_currencies_contains_common(): void { + $currencies = wu_get_currencies(); + + $this->assertArrayHasKey('USD', $currencies); + $this->assertArrayHasKey('EUR', $currencies); + $this->assertArrayHasKey('GBP', $currencies); + $this->assertArrayHasKey('BRL', $currencies); + $this->assertArrayHasKey('JPY', $currencies); + } + + /** + * Test wu_get_currency_symbol returns correct symbols. + */ + public function test_get_currency_symbol_usd(): void { + $symbol = wu_get_currency_symbol('USD'); + $this->assertEquals('$', $symbol); + } + + /** + * Test wu_get_currency_symbol returns correct symbols for EUR. + */ + public function test_get_currency_symbol_eur(): void { + $symbol = wu_get_currency_symbol('EUR'); + $this->assertEquals('€', $symbol); + } + + /** + * Test wu_get_currency_symbol returns correct symbols for GBP. + */ + public function test_get_currency_symbol_gbp(): void { + $symbol = wu_get_currency_symbol('GBP'); + $this->assertEquals('£', $symbol); + } + + /** + * Test wu_get_currency_symbol returns correct symbols for JPY. + */ + public function test_get_currency_symbol_jpy(): void { + $symbol = wu_get_currency_symbol('JPY'); + $this->assertEquals('¥', $symbol); + } + + /** + * Test wu_get_currency_symbol returns correct symbols for BRL. + */ + public function test_get_currency_symbol_brl(): void { + $symbol = wu_get_currency_symbol('BRL'); + $this->assertEquals('R$', $symbol); + } + + /** + * Test wu_get_currency_symbol returns code for unknown currency. + */ + public function test_get_currency_symbol_unknown(): void { + $symbol = wu_get_currency_symbol('XYZ'); + $this->assertEquals('XYZ', $symbol); + } + + /** + * Test wu_format_currency formats correctly. + */ + public function test_format_currency_basic(): void { + $formatted = wu_format_currency(99.99, 'USD', '%s %v', ',', '.', 2); + + $this->assertStringContainsString('$', $formatted); + $this->assertStringContainsString('99.99', $formatted); + } + + /** + * Test wu_format_currency with different format. + */ + public function test_format_currency_format_after_value(): void { + $formatted = wu_format_currency(100, 'EUR', '%v %s', '.', ',', 2); + + $this->assertStringContainsString('€', $formatted); + $this->assertStringContainsString('100', $formatted); + } + + /** + * Test wu_format_currency with thousands separator. + */ + public function test_format_currency_thousands(): void { + $formatted = wu_format_currency(1000.50, 'USD', '%s %v', ',', '.', 2); + + $this->assertStringContainsString('1,000.50', $formatted); + } + + /** + * Test wu_format_currency with zero value. + */ + public function test_format_currency_zero(): void { + $formatted = wu_format_currency(0, 'USD', '%s %v', ',', '.', 2); + + $this->assertStringContainsString('$', $formatted); + $this->assertStringContainsString('0.00', $formatted); + } + + /** + * Test wu_is_zero_decimal_currency returns true for JPY. + */ + public function test_is_zero_decimal_currency_jpy(): void { + $this->assertTrue(wu_is_zero_decimal_currency('JPY')); + } + + /** + * Test wu_is_zero_decimal_currency returns true for KRW. + */ + public function test_is_zero_decimal_currency_krw(): void { + $this->assertTrue(wu_is_zero_decimal_currency('KRW')); + } + + /** + * Test wu_is_zero_decimal_currency returns false for USD. + */ + public function test_is_zero_decimal_currency_usd(): void { + $this->assertFalse(wu_is_zero_decimal_currency('USD')); + } + + /** + * Test wu_is_zero_decimal_currency returns false for EUR. + */ + public function test_is_zero_decimal_currency_eur(): void { + $this->assertFalse(wu_is_zero_decimal_currency('EUR')); + } + + /** + * Test wu_stripe_get_currency_multiplier returns 100 for USD. + */ + public function test_stripe_currency_multiplier_usd(): void { + $multiplier = wu_stripe_get_currency_multiplier('USD'); + $this->assertEquals(100, $multiplier); + } + + /** + * Test wu_stripe_get_currency_multiplier returns 1 for JPY. + */ + public function test_stripe_currency_multiplier_jpy(): void { + $multiplier = wu_stripe_get_currency_multiplier('JPY'); + $this->assertEquals(1, $multiplier); + } + + /** + * Test wu_stripe_get_currency_multiplier returns 1 for KRW. + */ + public function test_stripe_currency_multiplier_krw(): void { + $multiplier = wu_stripe_get_currency_multiplier('KRW'); + $this->assertEquals(1, $multiplier); + } + + /** + * Test wu_currency_decimal_filter returns int. + */ + public function test_currency_decimal_filter_returns_int(): void { + $decimals = wu_currency_decimal_filter(); + $this->assertIsInt($decimals); + } + + /** + * Test wu_currency_decimal_filter with custom value. + */ + public function test_currency_decimal_filter_custom(): void { + $decimals = wu_currency_decimal_filter(3); + $this->assertIsInt($decimals); + } +} diff --git a/tests/WP_Ultimo/Functions/Date_Functions_Test.php b/tests/WP_Ultimo/Functions/Date_Functions_Test.php new file mode 100644 index 000000000..9716389e8 --- /dev/null +++ b/tests/WP_Ultimo/Functions/Date_Functions_Test.php @@ -0,0 +1,241 @@ +assertTrue(wu_validate_date('2024-01-15 10:30:00')); + } + + /** + * Test wu_validate_date returns false for invalid date. + */ + public function test_validate_date_invalid(): void { + $this->assertFalse(wu_validate_date('not-a-date')); + } + + /** + * Test wu_validate_date returns true for null. + */ + public function test_validate_date_null(): void { + $this->assertTrue(wu_validate_date(null)); + } + + /** + * Test wu_validate_date returns false for false. + */ + public function test_validate_date_false(): void { + $this->assertFalse(wu_validate_date(false)); + } + + /** + * Test wu_validate_date returns false for empty string. + */ + public function test_validate_date_empty_string(): void { + $this->assertFalse(wu_validate_date('')); + } + + /** + * Test wu_validate_date with custom format. + */ + public function test_validate_date_custom_format(): void { + $this->assertTrue(wu_validate_date('15/01/2024', 'd/m/Y')); + $this->assertFalse(wu_validate_date('2024-01-15', 'd/m/Y')); + } + + /** + * Test wu_date returns DateTime object. + */ + public function test_wu_date_returns_datetime(): void { + $date = wu_date('2024-01-15 10:30:00'); + + $this->assertInstanceOf(\DateTime::class, $date); + } + + /** + * Test wu_date returns current time for invalid input. + */ + public function test_wu_date_invalid_uses_now(): void { + $date = wu_date('invalid'); + + $this->assertInstanceOf(\DateTime::class, $date); + // Should be close to current time + $now = new \DateTime(); + $diff = $now->getTimestamp() - $date->getTimestamp(); + $this->assertLessThan(5, abs($diff)); // Within 5 seconds + } + + /** + * Test wu_date returns current time for false. + */ + public function test_wu_date_false_uses_now(): void { + $date = wu_date(false); + + $this->assertInstanceOf(\DateTime::class, $date); + } + + /** + * Test wu_get_days_ago returns correct value. + */ + public function test_get_days_ago(): void { + $today = date('Y-m-d H:i:s'); + $yesterday = date('Y-m-d H:i:s', strtotime('-1 day')); + + $days = wu_get_days_ago($yesterday, $today); + + $this->assertEquals(-1, $days); + } + + /** + * Test wu_get_days_ago with same date returns 0. + */ + public function test_get_days_ago_same_date(): void { + $date = '2024-01-15 10:00:00'; + $days = wu_get_days_ago($date, $date); + + $this->assertEquals(0, $days); + } + + /** + * Test wu_get_days_ago with future date. + */ + public function test_get_days_ago_future_date(): void { + $today = date('Y-m-d H:i:s'); + $tomorrow = date('Y-m-d H:i:s', strtotime('+1 day')); + + $days = wu_get_days_ago($tomorrow, $today); + + // Function always returns negative of days diff + $this->assertEquals(-1, $days); + } + + /** + * Test wu_get_current_time returns string. + */ + public function test_get_current_time_returns_string(): void { + $time = wu_get_current_time('mysql'); + + $this->assertIsString($time); + $this->assertNotEmpty($time); + } + + /** + * Test wu_filter_duration_unit for day singular. + */ + public function test_filter_duration_unit_day_singular(): void { + $unit = wu_filter_duration_unit('day', 1); + $this->assertEquals('Day', $unit); + } + + /** + * Test wu_filter_duration_unit for days plural. + */ + public function test_filter_duration_unit_day_plural(): void { + $unit = wu_filter_duration_unit('day', 5); + $this->assertEquals('Days', $unit); + } + + /** + * Test wu_filter_duration_unit for month singular. + */ + public function test_filter_duration_unit_month_singular(): void { + $unit = wu_filter_duration_unit('month', 1); + $this->assertEquals('Month', $unit); + } + + /** + * Test wu_filter_duration_unit for months plural. + */ + public function test_filter_duration_unit_month_plural(): void { + $unit = wu_filter_duration_unit('month', 3); + $this->assertEquals('Months', $unit); + } + + /** + * Test wu_filter_duration_unit for year singular. + */ + public function test_filter_duration_unit_year_singular(): void { + $unit = wu_filter_duration_unit('year', 1); + $this->assertEquals('Year', $unit); + } + + /** + * Test wu_filter_duration_unit for years plural. + */ + public function test_filter_duration_unit_year_plural(): void { + $unit = wu_filter_duration_unit('year', 2); + $this->assertEquals('Years', $unit); + } + + /** + * Test wu_filter_duration_unit for unknown unit. + */ + public function test_filter_duration_unit_unknown(): void { + $unit = wu_filter_duration_unit('week', 1); + $this->assertEquals('', $unit); + } + + /** + * Test wu_human_time_diff returns string. + */ + public function test_human_time_diff_returns_string(): void { + $result = wu_human_time_diff(date('Y-m-d H:i:s', strtotime('-1 hour'))); + + $this->assertIsString($result); + $this->assertNotEmpty($result); + } + + /** + * Test wu_convert_php_date_format_to_moment_js_format basic conversion. + */ + public function test_convert_date_format_year(): void { + $format = wu_convert_php_date_format_to_moment_js_format('Y'); + $this->assertEquals('YYYY', $format); + } + + /** + * Test wu_convert_php_date_format_to_moment_js_format for month. + */ + public function test_convert_date_format_month(): void { + $format = wu_convert_php_date_format_to_moment_js_format('m'); + $this->assertEquals('MM', $format); + } + + /** + * Test wu_convert_php_date_format_to_moment_js_format for day. + */ + public function test_convert_date_format_day(): void { + $format = wu_convert_php_date_format_to_moment_js_format('d'); + $this->assertEquals('DD', $format); + } + + /** + * Test wu_convert_php_date_format_to_moment_js_format for full date. + */ + public function test_convert_date_format_full(): void { + $format = wu_convert_php_date_format_to_moment_js_format('Y-m-d'); + $this->assertEquals('YYYY-MM-DD', $format); + } + + /** + * Test wu_convert_php_date_format_to_moment_js_format for time. + */ + public function test_convert_date_format_time(): void { + $format = wu_convert_php_date_format_to_moment_js_format('H:i:s'); + $this->assertEquals('HH:mm:ss', $format); + } +} diff --git a/tests/WP_Ultimo/Functions/Env_Functions_Test.php b/tests/WP_Ultimo/Functions/Env_Functions_Test.php new file mode 100644 index 000000000..b9e7550df --- /dev/null +++ b/tests/WP_Ultimo/Functions/Env_Functions_Test.php @@ -0,0 +1,70 @@ +assertEquals('frontend_class', $result); + } + + /** + * Test wu_env_picker returns backend content when admin. + */ + public function test_env_picker_backend(): void { + $result = wu_env_picker('frontend_class', 'backend_class', true); + + $this->assertEquals('backend_class', $result); + } + + /** + * Test wu_env_picker works with arrays. + */ + public function test_env_picker_with_arrays(): void { + $frontend = ['class1', 'class2']; + $backend = ['class3', 'class4']; + + $result = wu_env_picker($frontend, $backend, false); + $this->assertEquals($frontend, $result); + + $result = wu_env_picker($frontend, $backend, true); + $this->assertEquals($backend, $result); + } + + /** + * Test wu_env_picker works with booleans. + */ + public function test_env_picker_with_booleans(): void { + $result = wu_env_picker(true, false, false); + $this->assertTrue($result); + + $result = wu_env_picker(true, false, true); + $this->assertFalse($result); + } + + /** + * Test wu_env_picker works with numbers. + */ + public function test_env_picker_with_numbers(): void { + $result = wu_env_picker(100, 200, false); + $this->assertEquals(100, $result); + + $result = wu_env_picker(100, 200, true); + $this->assertEquals(200, $result); + } +} diff --git a/tests/WP_Ultimo/Functions/Financial_Functions_Test.php b/tests/WP_Ultimo/Functions/Financial_Functions_Test.php new file mode 100644 index 000000000..f801f21da --- /dev/null +++ b/tests/WP_Ultimo/Functions/Financial_Functions_Test.php @@ -0,0 +1,115 @@ +assertEqualsWithDelta(1 / 30, $result, 0.001); + } + + /** + * Test wu_convert_duration_unit_to_month with week. + */ + public function test_convert_duration_unit_to_month_week(): void { + $result = wu_convert_duration_unit_to_month('week'); + $this->assertEqualsWithDelta(1 / 4, $result, 0.001); + } + + /** + * Test wu_convert_duration_unit_to_month with month. + */ + public function test_convert_duration_unit_to_month_month(): void { + $result = wu_convert_duration_unit_to_month('month'); + $this->assertEquals(1, $result); + } + + /** + * Test wu_convert_duration_unit_to_month with year. + */ + public function test_convert_duration_unit_to_month_year(): void { + $result = wu_convert_duration_unit_to_month('year'); + $this->assertEquals(12, $result); + } + + /** + * Test wu_convert_duration_unit_to_month with unknown value. + */ + public function test_convert_duration_unit_to_month_unknown(): void { + $result = wu_convert_duration_unit_to_month('unknown'); + $this->assertEquals(1, $result); // Defaults to 1 + } + + /** + * Test wu_convert_duration_unit_to_month with empty string. + */ + public function test_convert_duration_unit_to_month_empty(): void { + $result = wu_convert_duration_unit_to_month(''); + $this->assertEquals(1, $result); // Defaults to 1 + } + + /** + * Test wu_calculate_arr is 12 times MRR. + */ + public function test_calculate_arr_is_12_times_mrr(): void { + // When there are no memberships, ARR should be 0 + $arr = wu_calculate_arr(); + $mrr = wu_calculate_mrr(); + + $this->assertEquals($mrr * 12, $arr); + } + + /** + * Test wu_calculate_mrr returns 0 with no memberships. + */ + public function test_calculate_mrr_no_memberships(): void { + $result = wu_calculate_mrr(); + $this->assertEquals(0.0, $result); + } + + /** + * Test wu_calculate_revenue returns 0 with no payments. + */ + public function test_calculate_revenue_no_payments(): void { + $result = wu_calculate_revenue(); + $this->assertEquals(0.0, $result); + } + + /** + * Test wu_calculate_refunds returns 0 with no refunds. + */ + public function test_calculate_refunds_no_refunds(): void { + $result = wu_calculate_refunds(); + $this->assertEquals(0.0, $result); + } + + /** + * Test wu_calculate_taxes_by_rate returns empty array with no line items. + */ + public function test_calculate_taxes_by_rate_empty(): void { + $result = wu_calculate_taxes_by_rate('2020-01-01', '2020-12-31'); + $this->assertIsArray($result); + } + + /** + * Test wu_calculate_financial_data_by_product returns array. + */ + public function test_calculate_financial_data_by_product_empty(): void { + $result = wu_calculate_financial_data_by_product('2020-01-01', '2020-12-31'); + $this->assertIsArray($result); + } +} diff --git a/tests/WP_Ultimo/Functions/Number_Helpers_Test.php b/tests/WP_Ultimo/Functions/Number_Helpers_Test.php new file mode 100644 index 000000000..eaa4da325 --- /dev/null +++ b/tests/WP_Ultimo/Functions/Number_Helpers_Test.php @@ -0,0 +1,134 @@ +assertEquals(123, wu_extract_number('123')); + $this->assertEquals(456, wu_extract_number('456')); + } + + /** + * Test wu_extract_number with text and number. + */ + public function test_extract_number_with_text(): void { + $this->assertEquals(42, wu_extract_number('The answer is 42')); + $this->assertEquals(100, wu_extract_number('100 items')); + $this->assertEquals(5, wu_extract_number('5GB')); + } + + /** + * Test wu_extract_number with number in middle of text. + */ + public function test_extract_number_in_middle(): void { + $this->assertEquals(25, wu_extract_number('There are 25 apples in the box')); + } + + /** + * Test wu_extract_number with multiple numbers. + */ + public function test_extract_number_multiple_numbers(): void { + // Should return the first number found + $this->assertEquals(10, wu_extract_number('10 cats and 20 dogs')); + } + + /** + * Test wu_extract_number with no number. + */ + public function test_extract_number_no_number(): void { + $this->assertEquals(0, wu_extract_number('no numbers here')); + $this->assertEquals(0, wu_extract_number('')); + } + + /** + * Test wu_extract_number with special characters. + */ + public function test_extract_number_special_chars(): void { + $this->assertEquals(99, wu_extract_number('$99.99')); + $this->assertEquals(100, wu_extract_number('100%')); + } + + /** + * Test wu_to_float with integer. + */ + public function test_to_float_with_integer(): void { + $this->assertEqualsWithDelta(100.0, wu_to_float(100), 0.001); + $this->assertEqualsWithDelta(0.0, wu_to_float(0), 0.001); + } + + /** + * Test wu_to_float with float. + */ + public function test_to_float_with_float(): void { + $this->assertEqualsWithDelta(99.99, wu_to_float(99.99), 0.001); + $this->assertEqualsWithDelta(0.5, wu_to_float(0.5), 0.001); + } + + /** + * Test wu_to_float with string number. + */ + public function test_to_float_with_string(): void { + $this->assertEqualsWithDelta(100.0, wu_to_float('100'), 0.001); + $this->assertEqualsWithDelta(99.99, wu_to_float('99.99', '.'), 0.001); + } + + /** + * Test wu_to_float with formatted string. + */ + public function test_to_float_formatted_string(): void { + $this->assertEqualsWithDelta(1000.0, wu_to_float('1,000', '.'), 0.001); + $this->assertEqualsWithDelta(1000.5, wu_to_float('1,000.50', '.'), 0.001); + } + + /** + * Test wu_to_float with currency symbol. + */ + public function test_to_float_with_currency(): void { + $this->assertEqualsWithDelta(99.99, wu_to_float('$99.99', '.'), 0.001); + $this->assertEqualsWithDelta(100.0, wu_to_float('€100', '.'), 0.001); + } + + /** + * Test wu_to_float with European format. + */ + public function test_to_float_european_format(): void { + // Note: wu_to_float keeps the decimal separator but doesn't convert it to period + // PHP's floatval() doesn't recognize comma as decimal separator + // So '99,99' with comma decimal separator stays '99,99' and floatval returns 99.0 + $result = wu_to_float('99,99', ','); + $this->assertIsFloat($result); + // The function preserves the comma but floatval truncates at it + $this->assertEqualsWithDelta(99.0, $result, 0.001); + } + + /** + * Test wu_to_float with negative numbers. + */ + public function test_to_float_negative(): void { + $this->assertEqualsWithDelta(-100.0, wu_to_float(-100), 0.001); + $this->assertEqualsWithDelta(-99.99, wu_to_float('-99.99', '.'), 0.001); + } + + /** + * Test wu_to_float returns float type. + */ + public function test_to_float_returns_float_type(): void { + $this->assertIsFloat(wu_to_float(100)); + $this->assertIsFloat(wu_to_float('100')); + $this->assertIsFloat(wu_to_float('99.99', '.')); + } +} diff --git a/tests/WP_Ultimo/Functions/Site_Functions_Test.php b/tests/WP_Ultimo/Functions/Site_Functions_Test.php new file mode 100644 index 000000000..120619153 --- /dev/null +++ b/tests/WP_Ultimo/Functions/Site_Functions_Test.php @@ -0,0 +1,183 @@ +assertEquals('mycoolsite', $url); + } + + /** + * Test wu_generate_site_url_from_title with special characters. + */ + public function test_generate_site_url_from_title_special_chars(): void { + $url = wu_generate_site_url_from_title("John's Blog!"); + + $this->assertEquals('johnsblog', $url); + } + + /** + * Test wu_generate_site_url_from_title with unicode. + */ + public function test_generate_site_url_from_title_unicode(): void { + $url = wu_generate_site_url_from_title('Café Blog'); + + // Non-ascii chars should be removed + $this->assertEquals('cafblog', $url); + } + + /** + * Test wu_generate_site_url_from_title with empty string. + */ + public function test_generate_site_url_from_title_empty(): void { + $url = wu_generate_site_url_from_title(''); + + $this->assertEquals('', $url); + } + + /** + * Test wu_generate_site_url_from_title with numbers at start. + */ + public function test_generate_site_url_from_title_starts_with_number(): void { + $url = wu_generate_site_url_from_title('123 Test Site'); + + // Should prepend 'site' if starts with number + $this->assertStringStartsWith('site', $url); + } + + /** + * Test wu_generate_site_url_from_title with dash at start. + */ + public function test_generate_site_url_from_title_starts_with_dash(): void { + $url = wu_generate_site_url_from_title('-test-site'); + + // Should prepend 'site' if starts with dash + $this->assertStringStartsWith('site', $url); + } + + /** + * Test wu_generate_site_title_from_email with normal email. + */ + public function test_generate_site_title_from_email_normal(): void { + $title = wu_generate_site_title_from_email('john.doe@example.com'); + + $this->assertEquals('John Doe', $title); + } + + /** + * Test wu_generate_site_title_from_email with underscore separator. + */ + public function test_generate_site_title_from_email_underscore(): void { + $title = wu_generate_site_title_from_email('jane_smith@example.com'); + + $this->assertEquals('Jane Smith', $title); + } + + /** + * Test wu_generate_site_title_from_email with generic prefix. + */ + public function test_generate_site_title_from_email_generic(): void { + $title = wu_generate_site_title_from_email('admin@company.com'); + + // Should fall back to domain + $this->assertStringContainsString('Company', $title); + } + + /** + * Test wu_generate_site_title_from_email with invalid email. + */ + public function test_generate_site_title_from_email_invalid(): void { + $title = wu_generate_site_title_from_email('not-an-email'); + + $this->assertEquals('', $title); + } + + /** + * Test wu_generate_site_title_from_email with empty string. + */ + public function test_generate_site_title_from_email_empty(): void { + $title = wu_generate_site_title_from_email(''); + + $this->assertEquals('', $title); + } + + /** + * Test wu_generate_site_title_from_email adds Site suffix for short names. + */ + public function test_generate_site_title_from_email_short_name(): void { + $title = wu_generate_site_title_from_email('jo@example.com'); + + // Short names should have " Site" appended + $this->assertStringContainsString('Site', $title); + } + + /** + * Test wu_get_current_site returns Site object. + */ + public function test_get_current_site_returns_site(): void { + $site = wu_get_current_site(); + + $this->assertInstanceOf(\WP_Ultimo\Models\Site::class, $site); + } + + /** + * Test wu_get_sites returns array. + */ + public function test_get_sites_returns_array(): void { + $sites = wu_get_sites(); + + $this->assertIsArray($sites); + } + + /** + * Test wu_get_sites with count returns int. + */ + public function test_get_sites_count(): void { + $count = wu_get_sites(['count' => true]); + + $this->assertIsInt($count); + } + + /** + * Test wu_get_site_templates returns array. + */ + public function test_get_site_templates_returns_array(): void { + $templates = wu_get_site_templates(); + + $this->assertIsArray($templates); + } + + /** + * Test wu_get_site with invalid id returns false. + */ + public function test_get_site_invalid_id(): void { + $site = wu_get_site(999999); + + $this->assertFalse($site); + } + + /** + * Test wu_get_site_by_hash with invalid hash returns false. + */ + public function test_get_site_by_hash_invalid(): void { + $site = wu_get_site_by_hash('invalid-hash'); + + $this->assertFalse($site); + } +} diff --git a/tests/WP_Ultimo/Functions/Sort_Helpers_Test.php b/tests/WP_Ultimo/Functions/Sort_Helpers_Test.php new file mode 100644 index 000000000..e9a520ee0 --- /dev/null +++ b/tests/WP_Ultimo/Functions/Sort_Helpers_Test.php @@ -0,0 +1,170 @@ + 10]; + $b = ['order' => 20]; + + $this->assertLessThan(0, wu_sort_by_column($a, $b)); + $this->assertGreaterThan(0, wu_sort_by_column($b, $a)); + } + + /** + * Test wu_sort_by_column with equal values. + */ + public function test_sort_by_column_equal(): void { + $a = ['order' => 10]; + $b = ['order' => 10]; + + $this->assertEquals(0, wu_sort_by_column($a, $b)); + } + + /** + * Test wu_sort_by_column with custom column. + */ + public function test_sort_by_column_custom(): void { + $a = ['priority' => 5]; + $b = ['priority' => 15]; + + $this->assertLessThan(0, wu_sort_by_column($a, $b, 'priority')); + $this->assertGreaterThan(0, wu_sort_by_column($b, $a, 'priority')); + } + + /** + * Test wu_sort_by_column with missing key defaults to 50. + */ + public function test_sort_by_column_missing_key(): void { + $a = ['order' => 10]; + $b = []; // No order key, defaults to 50 + + $this->assertLessThan(0, wu_sort_by_column($a, $b)); + + $c = ['order' => 100]; + $this->assertGreaterThan(0, wu_sort_by_column($c, $b)); // 100 > 50 + } + + /** + * Test wu_sort_by_column with both missing keys. + */ + public function test_sort_by_column_both_missing(): void { + $a = []; + $b = []; + + $this->assertEquals(0, wu_sort_by_column($a, $b)); // Both default to 50 + } + + /** + * Test wu_sort_by_order function. + */ + public function test_sort_by_order(): void { + $a = ['order' => 5]; + $b = ['order' => 25]; + + $this->assertLessThan(0, wu_sort_by_order($a, $b)); + $this->assertGreaterThan(0, wu_sort_by_order($b, $a)); + } + + /** + * Test using wu_sort_by_column with usort. + */ + public function test_sort_by_column_with_usort(): void { + $items = [ + ['name' => 'C', 'order' => 30], + ['name' => 'A', 'order' => 10], + ['name' => 'B', 'order' => 20], + ]; + + usort($items, fn($a, $b) => wu_sort_by_column($a, $b)); + + $this->assertEquals('A', $items[0]['name']); + $this->assertEquals('B', $items[1]['name']); + $this->assertEquals('C', $items[2]['name']); + } + + /** + * Test wu_set_order_from_index adds order to items. + */ + public function test_set_order_from_index_basic(): void { + $items = [ + ['name' => 'First'], + ['name' => 'Second'], + ['name' => 'Third'], + ]; + + $result = wu_set_order_from_index($items); + + $this->assertEquals(10, $result[0]['order']); + $this->assertEquals(20, $result[1]['order']); + $this->assertEquals(30, $result[2]['order']); + } + + /** + * Test wu_set_order_from_index preserves existing order. + */ + public function test_set_order_from_index_preserves_existing(): void { + $items = [ + ['name' => 'First', 'order' => 5], + ['name' => 'Second'], + ['name' => 'Third', 'order' => 100], + ]; + + $result = wu_set_order_from_index($items); + + $this->assertEquals(5, $result[0]['order']); // Preserved + // Index counter continues regardless of existing order values + $this->assertEquals(10, $result[1]['order']); // Added (first item without order gets index 1 * 10) + $this->assertEquals(100, $result[2]['order']); // Preserved + } + + /** + * Test wu_set_order_from_index with custom key. + */ + public function test_set_order_from_index_custom_key(): void { + $items = [ + ['name' => 'First'], + ['name' => 'Second'], + ]; + + $result = wu_set_order_from_index($items, 'priority'); + + $this->assertEquals(10, $result[0]['priority']); + $this->assertEquals(20, $result[1]['priority']); + } + + /** + * Test wu_set_order_from_index with empty array. + */ + public function test_set_order_from_index_empty(): void { + $result = wu_set_order_from_index([]); + + $this->assertEmpty($result); + $this->assertIsArray($result); + } + + /** + * Test wu_set_order_from_index with single item. + */ + public function test_set_order_from_index_single_item(): void { + $items = [['name' => 'Only']]; + + $result = wu_set_order_from_index($items); + + $this->assertEquals(10, $result[0]['order']); + } +} diff --git a/tests/WP_Ultimo/Functions/String_Helpers_Test.php b/tests/WP_Ultimo/Functions/String_Helpers_Test.php new file mode 100644 index 000000000..b8f87f54d --- /dev/null +++ b/tests/WP_Ultimo/Functions/String_Helpers_Test.php @@ -0,0 +1,147 @@ +assertTrue(wu_string_to_bool('yes')); + $this->assertTrue(wu_string_to_bool('YES')); + $this->assertTrue(wu_string_to_bool('Yes')); + $this->assertTrue(wu_string_to_bool('on')); + $this->assertTrue(wu_string_to_bool('ON')); + $this->assertTrue(wu_string_to_bool('true')); + $this->assertTrue(wu_string_to_bool('TRUE')); + $this->assertTrue(wu_string_to_bool('True')); + $this->assertTrue(wu_string_to_bool('1')); + $this->assertTrue(wu_string_to_bool(1)); + $this->assertTrue(wu_string_to_bool(true)); + } + + /** + * Test wu_string_to_bool with various falsy values. + */ + public function test_string_to_bool_falsy_values(): void { + $this->assertFalse(wu_string_to_bool('no')); + $this->assertFalse(wu_string_to_bool('NO')); + $this->assertFalse(wu_string_to_bool('off')); + $this->assertFalse(wu_string_to_bool('false')); + $this->assertFalse(wu_string_to_bool('0')); + $this->assertFalse(wu_string_to_bool(0)); + $this->assertFalse(wu_string_to_bool('')); + $this->assertFalse(wu_string_to_bool(false)); + } + + /** + * Test wu_string_to_bool with edge cases. + */ + public function test_string_to_bool_edge_cases(): void { + $this->assertFalse(wu_string_to_bool('random')); + $this->assertFalse(wu_string_to_bool('yesno')); + $this->assertFalse(wu_string_to_bool('2')); + } + + /** + * Test wu_slug_to_name with underscores. + */ + public function test_slug_to_name_with_underscores(): void { + $this->assertEquals('Discount Code', wu_slug_to_name('discount_code')); + $this->assertEquals('My Test Slug', wu_slug_to_name('my_test_slug')); + $this->assertEquals('Single', wu_slug_to_name('single')); + } + + /** + * Test wu_slug_to_name with dashes. + */ + public function test_slug_to_name_with_dashes(): void { + $this->assertEquals('Discount Code', wu_slug_to_name('discount-code')); + $this->assertEquals('My Test Slug', wu_slug_to_name('my-test-slug')); + } + + /** + * Test wu_slug_to_name with mixed separators. + */ + public function test_slug_to_name_mixed_separators(): void { + $this->assertEquals('My Test Slug', wu_slug_to_name('my-test_slug')); + $this->assertEquals('Foo Bar Baz', wu_slug_to_name('foo_bar-baz')); + } + + /** + * Test wu_slug_to_name with empty string. + */ + public function test_slug_to_name_empty_string(): void { + $this->assertEquals('', wu_slug_to_name('')); + } + + /** + * Test wu_replace_dashes function. + */ + public function test_replace_dashes(): void { + $this->assertEquals('foo_bar', wu_replace_dashes('foo-bar')); + $this->assertEquals('foo_bar_baz', wu_replace_dashes('foo-bar-baz')); + $this->assertEquals('no_dashes_here', wu_replace_dashes('no_dashes_here')); + $this->assertEquals('', wu_replace_dashes('')); + } + + /** + * Test wu_get_initials with two words. + */ + public function test_get_initials_two_words(): void { + $this->assertEquals('BP', wu_get_initials('Brazilian People')); + $this->assertEquals('JD', wu_get_initials('John Doe')); + $this->assertEquals('AB', wu_get_initials('Alice Bob')); + } + + /** + * Test wu_get_initials with single word. + */ + public function test_get_initials_single_word(): void { + $this->assertEquals('J', wu_get_initials('John')); + $this->assertEquals('A', wu_get_initials('Alice')); + } + + /** + * Test wu_get_initials with more than two words. + */ + public function test_get_initials_multiple_words(): void { + $this->assertEquals('JD', wu_get_initials('John David Smith')); + $this->assertEquals('AB', wu_get_initials('Alice Bob Carol')); + } + + /** + * Test wu_get_initials with custom max_size. + */ + public function test_get_initials_custom_max_size(): void { + $this->assertEquals('JDS', wu_get_initials('John David Smith', 3)); + $this->assertEquals('ABCD', wu_get_initials('Alice Bob Carol David', 4)); + $this->assertEquals('J', wu_get_initials('John David Smith', 1)); + } + + /** + * Test wu_get_initials with empty string. + */ + public function test_get_initials_empty_string(): void { + $this->assertEquals('', wu_get_initials('')); + } + + /** + * Test wu_get_initials returns uppercase. + */ + public function test_get_initials_returns_uppercase(): void { + $this->assertEquals('JD', wu_get_initials('john doe')); + $this->assertEquals('AB', wu_get_initials('alice bob')); + } +} diff --git a/tests/WP_Ultimo/Functions/Url_Helpers_Test.php b/tests/WP_Ultimo/Functions/Url_Helpers_Test.php new file mode 100644 index 000000000..b09412338 --- /dev/null +++ b/tests/WP_Ultimo/Functions/Url_Helpers_Test.php @@ -0,0 +1,142 @@ +assertEquals('example.com/path', wu_replace_scheme($url, '')); + } + + /** + * Test wu_replace_scheme removes https scheme. + */ + public function test_replace_scheme_removes_https(): void { + $url = 'https://example.com/path'; + $this->assertEquals('example.com/path', wu_replace_scheme($url, '')); + } + + /** + * Test wu_replace_scheme converts http to https. + */ + public function test_replace_scheme_http_to_https(): void { + $url = 'http://example.com/path'; + $this->assertEquals('https://example.com/path', wu_replace_scheme($url, 'https://')); + } + + /** + * Test wu_replace_scheme converts https to http. + */ + public function test_replace_scheme_https_to_http(): void { + $url = 'https://example.com/path'; + $this->assertEquals('http://example.com/path', wu_replace_scheme($url, 'http://')); + } + + /** + * Test wu_replace_scheme with URL without scheme. + */ + public function test_replace_scheme_no_scheme(): void { + $url = 'example.com/path'; + $this->assertEquals('example.com/path', wu_replace_scheme($url, '')); + } + + /** + * Test wu_replace_scheme with protocol-relative URL. + */ + public function test_replace_scheme_protocol_relative(): void { + $url = '//example.com/path'; + $this->assertEquals('//example.com/path', wu_replace_scheme($url, '')); + } + + /** + * Test wu_network_admin_url generates correct URL. + */ + public function test_network_admin_url_basic(): void { + $url = wu_network_admin_url('wp-ultimo'); + $this->assertStringContainsString('admin.php?page=wp-ultimo', $url); + } + + /** + * Test wu_network_admin_url with query parameters. + */ + public function test_network_admin_url_with_query_params(): void { + $url = wu_network_admin_url('wp-ultimo', ['tab' => 'settings', 'id' => 123]); + $this->assertStringContainsString('admin.php?page=wp-ultimo', $url); + $this->assertStringContainsString('tab=settings', $url); + $this->assertStringContainsString('id=123', $url); + } + + /** + * Test wu_network_admin_url returns network admin URL. + */ + public function test_network_admin_url_is_network_admin(): void { + $url = wu_network_admin_url('wp-ultimo'); + $this->assertStringContainsString(network_admin_url(), $url); + } + + /** + * Test wu_ajax_url generates URL with wu-ajax parameter. + */ + public function test_ajax_url_has_wu_ajax_param(): void { + $url = wu_ajax_url(); + $this->assertStringContainsString('wu-ajax=1', $url); + } + + /** + * Test wu_ajax_url includes nonce. + */ + public function test_ajax_url_has_nonce(): void { + $url = wu_ajax_url(); + $this->assertStringContainsString('r=', $url); + } + + /** + * Test wu_ajax_url with when parameter. + */ + public function test_ajax_url_with_when_param(): void { + $url = wu_ajax_url('plugins_loaded'); + $this->assertStringContainsString('wu-when=', $url); + // The base64 value is embedded in the URL (without padding = char) + $this->assertMatchesRegularExpression('/wu-when=[a-zA-Z0-9]+/', $url); + } + + /** + * Test wu_ajax_url without when parameter. + */ + public function test_ajax_url_without_when_param(): void { + $url = wu_ajax_url(null); + $this->assertStringNotContainsString('wu-when=', $url); + } + + /** + * Test wu_ajax_url with custom query args. + */ + public function test_ajax_url_with_query_args(): void { + $url = wu_ajax_url(null, ['action' => 'test_action', 'id' => 42]); + $this->assertStringContainsString('action=test_action', $url); + $this->assertStringContainsString('id=42', $url); + } + + /** + * Test wu_ajax_url with specific site_id. + */ + public function test_ajax_url_with_site_id(): void { + $url = wu_ajax_url(null, [], 1); + $this->assertIsString($url); + $this->assertStringContainsString('wu-ajax=1', $url); + } +} diff --git a/tests/WP_Ultimo/Gateways/Free_Gateway_Test.php b/tests/WP_Ultimo/Gateways/Free_Gateway_Test.php new file mode 100644 index 000000000..25ac4df6e --- /dev/null +++ b/tests/WP_Ultimo/Gateways/Free_Gateway_Test.php @@ -0,0 +1,211 @@ +gateway = new Free_Gateway(); + } + + /** + * Test gateway ID is free. + */ + public function test_gateway_id(): void { + $this->assertEquals('free', $this->gateway->get_id()); + } + + /** + * Test process_checkout with new subscription. + */ + public function test_process_checkout_new(): void { + // Create test user + $user_id = self::factory()->user->create([ + 'user_login' => 'freegatewayuser', + 'user_email' => 'freegateway@example.com', + ]); + + // Create customer + $customer = wu_create_customer([ + 'user_id' => $user_id, + 'email_verification' => 'none', + ]); + + $this->assertNotWPError($customer); + + // Create product + $product = wu_create_product([ + 'name' => 'Free Plan', + 'slug' => 'free-plan-' . wp_generate_uuid4(), + 'pricing_type' => 'free', + 'amount' => 0, + 'type' => 'plan', + ]); + + $this->assertNotWPError($product); + + // Create membership + $membership = wu_create_membership([ + 'customer_id' => $customer->get_id(), + 'plan_id' => $product->get_id(), + 'status' => Membership_Status::PENDING, + 'recurring' => false, + 'amount' => 0, + 'skip_validation' => true, + ]); + + $this->assertNotWPError($membership); + + // Create payment + $payment = wu_create_payment([ + 'customer_id' => $customer->get_id(), + 'membership_id' => $membership->get_id(), + 'currency' => 'USD', + 'subtotal' => 0, + 'total' => 0, + 'status' => Payment_Status::PENDING, + 'gateway' => 'free', + ]); + + $this->assertNotWPError($payment); + + // Create cart mock + $cart = new \WP_Ultimo\Checkout\Cart([ + 'products' => [$product->get_id()], + ]); + + // Process checkout + $this->gateway->process_checkout($payment, $membership, $customer, $cart, 'new'); + + // Reload from database + $updated_payment = wu_get_payment($payment->get_id()); + $updated_membership = wu_get_membership($membership->get_id()); + + // Assert payment is completed + $this->assertEquals(Payment_Status::COMPLETED, $updated_payment->get_status()); + + // Assert membership gateway is set + $this->assertEquals('free', $updated_membership->get_gateway()); + } + + /** + * Test process_cancellation does nothing. + */ + public function test_process_cancellation(): void { + // Create test user + $user_id = self::factory()->user->create([ + 'user_login' => 'canceluser', + 'user_email' => 'cancel@example.com', + ]); + + $customer = wu_create_customer([ + 'user_id' => $user_id, + 'email_verification' => 'none', + ]); + + $this->assertNotWPError($customer); + + $product = wu_create_product([ + 'name' => 'Cancel Plan', + 'slug' => 'cancel-plan-' . wp_generate_uuid4(), + 'pricing_type' => 'free', + 'amount' => 0, + 'type' => 'plan', + ]); + + $this->assertNotWPError($product); + + $membership = wu_create_membership([ + 'customer_id' => $customer->get_id(), + 'plan_id' => $product->get_id(), + 'status' => Membership_Status::ACTIVE, + 'recurring' => false, + 'amount' => 0, + 'skip_validation' => true, + ]); + + $this->assertNotWPError($membership); + + // Process cancellation - should not throw + $result = $this->gateway->process_cancellation($membership, $customer); + + $this->assertNull($result); + } + + /** + * Test process_refund does nothing. + */ + public function test_process_refund(): void { + // Create test user + $user_id = self::factory()->user->create([ + 'user_login' => 'refunduser', + 'user_email' => 'refund@example.com', + ]); + + $customer = wu_create_customer([ + 'user_id' => $user_id, + 'email_verification' => 'none', + ]); + + $this->assertNotWPError($customer); + + $product = wu_create_product([ + 'name' => 'Refund Plan', + 'slug' => 'refund-plan-' . wp_generate_uuid4(), + 'pricing_type' => 'free', + 'amount' => 0, + 'type' => 'plan', + ]); + + $this->assertNotWPError($product); + + $membership = wu_create_membership([ + 'customer_id' => $customer->get_id(), + 'plan_id' => $product->get_id(), + 'status' => Membership_Status::ACTIVE, + 'recurring' => false, + 'amount' => 0, + 'skip_validation' => true, + ]); + + $this->assertNotWPError($membership); + + $payment = wu_create_payment([ + 'customer_id' => $customer->get_id(), + 'membership_id' => $membership->get_id(), + 'currency' => 'USD', + 'subtotal' => 0, + 'total' => 0, + 'status' => Payment_Status::COMPLETED, + 'gateway' => 'free', + ]); + + $this->assertNotWPError($payment); + + // Process refund - should not throw + $result = $this->gateway->process_refund(0, $payment, $membership, $customer); + + $this->assertNull($result); + } +} diff --git a/tests/WP_Ultimo/Helpers/Validation_Rules/Country_Test.php b/tests/WP_Ultimo/Helpers/Validation_Rules/Country_Test.php new file mode 100644 index 000000000..32c513375 --- /dev/null +++ b/tests/WP_Ultimo/Helpers/Validation_Rules/Country_Test.php @@ -0,0 +1,141 @@ +rule = new Country(); + } + + /** + * Test valid country code US. + */ + public function test_valid_country_us(): void { + $this->assertTrue($this->rule->check('US')); + } + + /** + * Test valid country code lowercase. + */ + public function test_valid_country_lowercase(): void { + $this->assertTrue($this->rule->check('us')); + } + + /** + * Test valid country code UK. + */ + public function test_valid_country_gb(): void { + $this->assertTrue($this->rule->check('GB')); + } + + /** + * Test valid country code Canada. + */ + public function test_valid_country_ca(): void { + $this->assertTrue($this->rule->check('CA')); + } + + /** + * Test valid country code Germany. + */ + public function test_valid_country_de(): void { + $this->assertTrue($this->rule->check('DE')); + } + + /** + * Test valid country code Brazil. + */ + public function test_valid_country_br(): void { + $this->assertTrue($this->rule->check('BR')); + } + + /** + * Test valid country code Australia. + */ + public function test_valid_country_au(): void { + $this->assertTrue($this->rule->check('AU')); + } + + /** + * Test invalid country code. + */ + public function test_invalid_country_code(): void { + $this->assertFalse($this->rule->check('XX')); + } + + /** + * Test invalid country three letter code. + */ + public function test_invalid_country_three_letters(): void { + $this->assertFalse($this->rule->check('USA')); + } + + /** + * Test invalid country numeric. + */ + public function test_invalid_country_numeric(): void { + $this->assertFalse($this->rule->check('123')); + } + + /** + * Test empty string is valid. + */ + public function test_empty_string_is_valid(): void { + $this->assertTrue($this->rule->check('')); + } + + /** + * Test null value is valid. + */ + public function test_null_is_valid(): void { + $this->assertTrue($this->rule->check(null)); + } + + /** + * Test false value is valid. + */ + public function test_false_is_valid(): void { + $this->assertTrue($this->rule->check(false)); + } + + /** + * Test mixed case country code. + */ + public function test_mixed_case_country(): void { + $this->assertTrue($this->rule->check('Us')); + } + + /** + * Test valid country code France. + */ + public function test_valid_country_fr(): void { + $this->assertTrue($this->rule->check('FR')); + } + + /** + * Test valid country code Japan. + */ + public function test_valid_country_jp(): void { + $this->assertTrue($this->rule->check('JP')); + } +} diff --git a/tests/WP_Ultimo/Helpers/Validation_Rules/Domain_Test.php b/tests/WP_Ultimo/Helpers/Validation_Rules/Domain_Test.php new file mode 100644 index 000000000..fcd42840e --- /dev/null +++ b/tests/WP_Ultimo/Helpers/Validation_Rules/Domain_Test.php @@ -0,0 +1,159 @@ +rule = new Domain(); + } + + /** + * Test valid domain with com TLD. + */ + public function test_valid_domain_com(): void { + $this->assertTrue($this->rule->check('example.com')); + } + + /** + * Test valid domain with org TLD. + */ + public function test_valid_domain_org(): void { + $this->assertTrue($this->rule->check('example.org')); + } + + /** + * Test valid domain with net TLD. + */ + public function test_valid_domain_net(): void { + $this->assertTrue($this->rule->check('example.net')); + } + + /** + * Test valid domain with subdomain. + */ + public function test_valid_domain_with_subdomain(): void { + $this->assertTrue($this->rule->check('sub.example.com')); + } + + /** + * Test valid domain with multiple subdomains. + */ + public function test_valid_domain_multiple_subdomains(): void { + $this->assertTrue($this->rule->check('deep.sub.example.com')); + } + + /** + * Test valid domain with hyphen. + */ + public function test_valid_domain_with_hyphen(): void { + $this->assertTrue($this->rule->check('my-site.com')); + } + + /** + * Test valid domain with numbers. + */ + public function test_valid_domain_with_numbers(): void { + $this->assertTrue($this->rule->check('site123.com')); + } + + /** + * Test valid domain with country TLD. + */ + public function test_valid_domain_country_tld(): void { + $this->assertTrue($this->rule->check('example.co.uk')); + } + + /** + * Test invalid domain starting with hyphen. + */ + public function test_invalid_domain_starts_with_hyphen(): void { + $this->assertFalse($this->rule->check('-example.com')); + } + + /** + * Test invalid domain without TLD. + */ + public function test_invalid_domain_no_tld(): void { + $this->assertFalse($this->rule->check('example')); + } + + /** + * Test invalid domain with spaces. + */ + public function test_invalid_domain_with_spaces(): void { + $this->assertFalse($this->rule->check('example site.com')); + } + + /** + * Test invalid domain with underscore. + */ + public function test_invalid_domain_with_underscore(): void { + $this->assertFalse($this->rule->check('example_site.com')); + } + + /** + * Test invalid domain with special characters. + */ + public function test_invalid_domain_special_chars(): void { + $this->assertFalse($this->rule->check('example!@#.com')); + } + + /** + * Test empty string is invalid. + */ + public function test_invalid_empty_string(): void { + $this->assertFalse($this->rule->check('')); + } + + /** + * Test domain ending with dot is invalid. + */ + public function test_invalid_domain_ending_with_dot(): void { + $this->assertFalse($this->rule->check('example.com.')); + } + + /** + * Test valid domain with new gTLD. + */ + public function test_valid_domain_new_gtld(): void { + $this->assertTrue($this->rule->check('example.dev')); + } + + /** + * Test valid domain with long TLD. + */ + public function test_valid_domain_long_tld(): void { + $this->assertTrue($this->rule->check('example.technology')); + } + + /** + * Test message property exists. + */ + public function test_message_property(): void { + $reflection = new \ReflectionClass($this->rule); + $property = $reflection->getProperty('message'); + $property->setAccessible(true); + + $this->assertIsString($property->getValue($this->rule)); + } +} diff --git a/tests/WP_Ultimo/Helpers/Validation_Rules/State_Test.php b/tests/WP_Ultimo/Helpers/Validation_Rules/State_Test.php new file mode 100644 index 000000000..63b79dbe9 --- /dev/null +++ b/tests/WP_Ultimo/Helpers/Validation_Rules/State_Test.php @@ -0,0 +1,127 @@ +rule = new State(); + } + + /** + * Test valid US state. + */ + public function test_valid_us_state(): void { + $this->rule->setParameters(['country' => 'US']); + $this->assertTrue($this->rule->check('CA')); + } + + /** + * Test valid US state lowercase. + */ + public function test_valid_us_state_lowercase(): void { + $this->rule->setParameters(['country' => 'US']); + $this->assertTrue($this->rule->check('ca')); + } + + /** + * Test valid US state New York. + */ + public function test_valid_us_state_ny(): void { + $this->rule->setParameters(['country' => 'US']); + $this->assertTrue($this->rule->check('NY')); + } + + /** + * Test valid US state Texas. + */ + public function test_valid_us_state_tx(): void { + $this->rule->setParameters(['country' => 'US']); + $this->assertTrue($this->rule->check('TX')); + } + + /** + * Test empty state without country is valid. + */ + public function test_empty_state_no_country(): void { + $this->assertTrue($this->rule->check('')); + } + + /** + * Test empty state with country is valid. + */ + public function test_empty_state_with_country(): void { + $this->rule->setParameters(['country' => 'US']); + $this->assertTrue($this->rule->check('')); + } + + /** + * Test null state is valid. + */ + public function test_null_state(): void { + $this->rule->setParameters(['country' => 'US']); + $this->assertTrue($this->rule->check(null)); + } + + /** + * Test state without country param is valid. + */ + public function test_state_without_country_param(): void { + $this->assertTrue($this->rule->check('CA')); + } + + /** + * Test fillable params. + */ + public function test_fillable_params(): void { + $reflection = new \ReflectionClass($this->rule); + $property = $reflection->getProperty('fillableParams'); + $property->setAccessible(true); + + $params = $property->getValue($this->rule); + $this->assertContains('country', $params); + } + + /** + * Test mixed case state. + */ + public function test_mixed_case_state(): void { + $this->rule->setParameters(['country' => 'US']); + $this->assertTrue($this->rule->check('Ny')); + } + + /** + * Test valid Canadian province. + */ + public function test_valid_canadian_province(): void { + $this->rule->setParameters(['country' => 'CA']); + $this->assertTrue($this->rule->check('ON')); + } + + /** + * Test valid Canadian province BC. + */ + public function test_valid_canadian_province_bc(): void { + $this->rule->setParameters(['country' => 'CA']); + $this->assertTrue($this->rule->check('BC')); + } +} diff --git a/tests/WP_Ultimo/SSO/SSO_Test.php b/tests/WP_Ultimo/SSO/SSO_Test.php new file mode 100644 index 000000000..85bf6bdb5 --- /dev/null +++ b/tests/WP_Ultimo/SSO/SSO_Test.php @@ -0,0 +1,633 @@ +assertTrue($sso->is_enabled()); + } + + public function test_is_enabled_returns_false_when_filter_returns_false(): void { + remove_all_filters('wu_sso_enabled'); + add_filter('wu_sso_enabled', '__return_false'); + + $sso = SSO::get_instance(); + + $this->assertFalse($sso->is_enabled()); + } + + // ------------------------------------------------------------------ + // force_secure_login_cookie + // ------------------------------------------------------------------ + + public function test_force_secure_login_cookie_returns_boolean(): void { + $sso = SSO::get_instance(); + $result = $sso->force_secure_login_cookie(); + + $this->assertIsBool($result); + } + + // ------------------------------------------------------------------ + // get_url_path + // ------------------------------------------------------------------ + + public function test_get_url_path_defaults_to_sso(): void { + $sso = SSO::get_instance(); + + $this->assertSame('sso', $sso->get_url_path()); + } + + public function test_get_url_path_appends_action(): void { + $sso = SSO::get_instance(); + + $this->assertSame('sso-grant', $sso->get_url_path('grant')); + $this->assertSame('sso-login', $sso->get_url_path('login')); + } + + public function test_get_url_path_respects_filter(): void { + add_filter( + 'wu_sso_get_url_path', + function () { + return 'mysso'; + } + ); + + $sso = SSO::get_instance(); + + $this->assertSame('mysso', $sso->get_url_path()); + $this->assertSame('mysso-grant', $sso->get_url_path('grant')); + } + + // ------------------------------------------------------------------ + // encode / decode + // ------------------------------------------------------------------ + + public function test_encode_returns_non_numeric_string(): void { + $sso = SSO::get_instance(); + $encoded = $sso->encode(42, $sso->salt()); + + $this->assertIsString($encoded); + $this->assertNotSame('42', $encoded); + } + + public function test_decode_returns_original_value(): void { + $sso = SSO::get_instance(); + $salt = $sso->salt(); + $encoded = $sso->encode(999, $salt); + $decoded = $sso->decode($encoded, $salt); + + $this->assertSame(999, $decoded); + } + + public function test_different_salts_produce_different_encodings(): void { + $sso = SSO::get_instance(); + + $enc_a = $sso->encode(1, 'salt-a'); + $enc_b = $sso->encode(1, 'salt-b'); + + $this->assertNotSame($enc_a, $enc_b); + } + + // ------------------------------------------------------------------ + // get_strategy + // ------------------------------------------------------------------ + + public function test_get_strategy_returns_string(): void { + $sso = SSO::get_instance(); + + $strategy = $sso->get_strategy(); + + $this->assertContains($strategy, ['ajax', 'redirect']); + } + + public function test_get_strategy_respects_filter(): void { + add_filter( + 'wu_sso_get_strategy', + function () { + return 'ajax'; + } + ); + + $sso = SSO::get_instance(); + + $this->assertSame('ajax', $sso->get_strategy()); + } + + // ------------------------------------------------------------------ + // get_return_type + // ------------------------------------------------------------------ + + public function test_get_return_type_defaults_to_redirect(): void { + unset($_REQUEST['return_type']); + + $sso = SSO::get_instance(); + + $this->assertSame('redirect', $sso->get_return_type()); + } + + public function test_get_return_type_accepts_jsonp(): void { + $_REQUEST['return_type'] = 'jsonp'; + + $sso = SSO::get_instance(); + + $this->assertSame('jsonp', $sso->get_return_type()); + } + + public function test_get_return_type_accepts_json(): void { + $_REQUEST['return_type'] = 'json'; + + $sso = SSO::get_instance(); + + $this->assertSame('json', $sso->get_return_type()); + } + + public function test_get_return_type_rejects_invalid_and_defaults_to_redirect(): void { + $_REQUEST['return_type'] = 'xml'; + + $sso = SSO::get_instance(); + + $this->assertSame('redirect', $sso->get_return_type()); + } + + // ------------------------------------------------------------------ + // add_sso_removable_query_args + // ------------------------------------------------------------------ + + public function test_add_sso_removable_query_args_adds_sso_path(): void { + $sso = SSO::get_instance(); + $args = $sso->add_sso_removable_query_args([]); + + $this->assertContains('sso', $args); + } + + public function test_add_sso_removable_query_args_preserves_existing(): void { + $sso = SSO::get_instance(); + $args = $sso->add_sso_removable_query_args(['existing_arg']); + + $this->assertContains('existing_arg', $args); + $this->assertContains('sso', $args); + } + + // ------------------------------------------------------------------ + // target_user_id + // ------------------------------------------------------------------ + + public function test_set_and_get_target_user_id(): void { + $sso = SSO::get_instance(); + + $this->assertNull($sso->get_target_user_id()); + + $sso->set_target_user_id(42); + $this->assertSame(42, $sso->get_target_user_id()); + + // Reset for other tests. + $sso->set_target_user_id(null); + } + + // ------------------------------------------------------------------ + // calculate_secret_from_date + // ------------------------------------------------------------------ + + public function test_calculate_secret_produces_consistent_hash(): void { + $sso = SSO::get_instance(); + + $secret_a = $sso->calculate_secret_from_date('2024-06-15 12:30:00'); + $secret_b = $sso->calculate_secret_from_date('2024-06-15 12:30:00'); + + $this->assertSame($secret_a, $secret_b); + } + + public function test_calculate_secret_differs_for_different_dates(): void { + $sso = SSO::get_instance(); + + $secret_a = $sso->calculate_secret_from_date('2024-01-01 00:00:00'); + $secret_b = $sso->calculate_secret_from_date('2024-12-31 23:59:59'); + + $this->assertNotSame($secret_a, $secret_b); + } + + public function test_calculate_secret_throws_on_invalid_date(): void { + $sso = SSO::get_instance(); + + $this->expectException(\WP_Ultimo\SSO\Exception\SSO_Exception::class); + + $sso->calculate_secret_from_date('garbage'); + } + + // ------------------------------------------------------------------ + // with_sso (static) + // ------------------------------------------------------------------ + + public function test_with_sso_adds_sso_login_param(): void { + $url = 'https://example.com/checkout'; + $result = SSO::with_sso($url); + + $this->assertStringContainsString('sso=login', $result); + } + + public function test_with_sso_preserves_existing_query_params(): void { + $url = 'https://example.com/checkout?plan=pro'; + $result = SSO::with_sso($url); + + $this->assertStringContainsString('plan=pro', $result); + $this->assertStringContainsString('sso=login', $result); + } + + public function test_with_sso_returns_original_url_when_disabled(): void { + remove_all_filters('wu_sso_enabled'); + add_filter('wu_sso_enabled', '__return_false'); + + $url = 'https://example.com/checkout'; + $result = SSO::with_sso($url); + + $this->assertSame($url, $result); + } + + // ------------------------------------------------------------------ + // get_final_return_url + // ------------------------------------------------------------------ + + public function test_get_final_return_url_includes_done_and_wp_login(): void { + $sso = SSO::get_instance(); + $url = 'https://example.com/sso?redirect_to=https%3A%2F%2Fexample.com%2Fdashboard'; + + $final = $sso->get_final_return_url($url); + + $this->assertStringContainsString('sso=done', $final); + $this->assertStringContainsString('wp-login.php', $final); + $this->assertStringContainsString('redirect_to=', $final); + } + + public function test_get_final_return_url_strips_sso_path_from_url_path(): void { + $sso = SSO::get_instance(); + $url = 'https://sub.example.com/sso'; + $final = $sso->get_final_return_url($url); + + // The path portion should not end with /sso/. + $parsed_path = wp_parse_url($final, PHP_URL_PATH); + $this->assertStringNotContainsString('/sso/', $parsed_path); + } + + // ------------------------------------------------------------------ + // get_broker_by_id + // ------------------------------------------------------------------ + + public function test_get_broker_by_id_returns_null_for_invalid_id(): void { + $sso = SSO::get_instance(); + + $this->assertNull($sso->get_broker_by_id('completely-invalid')); + } + + public function test_get_broker_by_id_returns_array_for_valid_site(): void { + $blog_id = get_current_blog_id(); + + $sso = SSO::get_instance(); + $coded = $sso->encode($blog_id, $sso->salt()); + $result = $sso->get_broker_by_id($coded); + + $this->assertIsArray($result); + $this->assertArrayHasKey('secret', $result); + $this->assertArrayHasKey('domains', $result); + $this->assertIsArray($result['domains']); + $this->assertNotEmpty($result['domains']); + } + + // ------------------------------------------------------------------ + // handle_broker early returns + // ------------------------------------------------------------------ + + public function test_handle_broker_returns_early_on_main_site(): void { + // On the main site, handle_broker should return without doing anything. + $this->assertTrue(is_main_site(), 'Test expects to run on main site'); + + $sso = SSO::get_instance(); + + // Should return without exit — no exception, no redirect. + $sso->handle_broker('redirect'); + + // If we reach here, the early return worked. + $this->assertTrue(true); + } + + // ------------------------------------------------------------------ + // handle_broker sets wu_sso_denied cookie on failure + // ------------------------------------------------------------------ + + /** + * Verify that the source code sets the wu_sso_denied cookie + * when sso_verify is 'invalid', preventing redirect loops. + */ + public function test_handle_broker_source_sets_denied_cookie_on_invalid_verify(): void { + $source = file_get_contents( + dirname(__DIR__, 3) . '/inc/sso/class-sso.php' + ); + + // The 'invalid' === $verify_code branch must setcookie('wu_sso_denied', ...) + $pattern = "/'invalid'\s*===\s*\\\$verify_code.*?setcookie\(\s*'wu_sso_denied'/s"; + $this->assertMatchesRegularExpression( + $pattern, + $source, + 'handle_broker must set wu_sso_denied cookie when sso_verify is invalid' + ); + } + + /** + * Verify that $_COOKIE['wu_sso_denied'] is also set for the current request, + * so that later code (handle_auth_redirect, enqueue_script) sees it immediately. + */ + public function test_handle_broker_source_sets_cookie_superglobal_on_invalid_verify(): void { + $source = file_get_contents( + dirname(__DIR__, 3) . '/inc/sso/class-sso.php' + ); + + $pattern = "/'invalid'\s*===\s*\\\$verify_code.*?\\\$_COOKIE\s*\[\s*'wu_sso_denied'\s*\]/s"; + $this->assertMatchesRegularExpression( + $pattern, + $source, + 'handle_broker must set $_COOKIE[wu_sso_denied] for the current request' + ); + } + + /** + * Verify that the invalid verify branch redirects to return_url + * instead of leaving the user on the /sso 404 page. + */ + public function test_handle_broker_source_redirects_to_return_url_on_invalid_verify(): void { + $source = file_get_contents( + dirname(__DIR__, 3) . '/inc/sso/class-sso.php' + ); + + $pattern = "/'invalid'\s*===\s*\\\$verify_code.*?wp_safe_redirect\s*\(\s*\\\$return_url/s"; + $this->assertMatchesRegularExpression( + $pattern, + $source, + 'handle_broker must redirect to return_url when sso_verify is invalid' + ); + } + + // ------------------------------------------------------------------ + // handle_requests early return + // ------------------------------------------------------------------ + + public function test_handle_requests_returns_early_without_sso_action(): void { + // With no SSO-related request params, handle_requests should return immediately. + $sso = SSO::get_instance(); + + $sso->handle_requests(); + + // If we reach here without exit, the early return worked. + $this->assertTrue(true); + } + + // ------------------------------------------------------------------ + // Session handler + // ------------------------------------------------------------------ + + public function test_session_handler_is_active_returns_false(): void { + $sso = SSO::get_instance(); + $handler = new SSO_Session_Handler($sso); + + $this->assertFalse($handler->isActive()); + } + + public function test_session_handler_get_id_reads_broker_param(): void { + $_REQUEST['broker'] = 'test-broker-id'; + + $sso = SSO::get_instance(); + $handler = new SSO_Session_Handler($sso); + + $this->assertSame('test-broker-id', $handler->getId()); + } + + public function test_session_handler_start_throws_when_not_logged_in(): void { + wp_set_current_user(0); + + $this->expectException(\WP_Ultimo\SSO\Exception\SSO_Session_Exception::class); + $this->expectExceptionCode(401); + + $sso = SSO::get_instance(); + $handler = new SSO_Session_Handler($sso); + + $handler->start(); + } + + public function test_session_handler_resume_sets_target_user_id_from_transient(): void { + $user_id = self::factory()->user->create(); + $site_id = get_current_blog_id(); + + $sso = SSO::get_instance(); + $salt = $sso->salt(); + $broker = $sso->encode($site_id, $salt); + + set_site_transient("sso-{$broker}-{$site_id}", $user_id, 180); + + $handler = new SSO_Session_Handler($sso); + $handler->resume($broker); + + $this->assertSame($user_id, $sso->get_target_user_id()); + + // Clean up. + $sso->set_target_user_id(null); + delete_site_transient("sso-{$broker}-{$site_id}"); + } + + public function test_session_handler_resume_does_not_set_user_when_transient_missing(): void { + $sso = SSO::get_instance(); + $salt = $sso->salt(); + + // Reset target. + $sso->set_target_user_id(null); + + $broker = $sso->encode(9999, $salt); + + $handler = new SSO_Session_Handler($sso); + $handler->resume($broker); + + $this->assertNull($sso->get_target_user_id()); + } + + // ------------------------------------------------------------------ + // SSO Broker + // ------------------------------------------------------------------ + + public function test_broker_get_attach_url_includes_required_params(): void { + $sso = SSO::get_instance(); + + $blog_id = get_current_blog_id(); + $blog = get_blog_details($blog_id); + $date = $blog ? $blog->registered : '2024-01-01 00:00:00'; + $secret = $sso->calculate_secret_from_date($date); + $url = trailingslashit(network_home_url()) . $sso->get_url_path('grant'); + $broker_id = $sso->encode($blog_id, $sso->salt()); + $broker = new SSO_Broker($url, $broker_id, $secret); + $broker = $broker->withTokenIn(new \ArrayObject()); + + $attach_url = $broker->getAttachUrl(['return_url' => 'https://example.com/page']); + + $parts = wp_parse_url($attach_url); + parse_str($parts['query'] ?? '', $query); + + $this->assertArrayHasKey('broker', $query); + $this->assertArrayHasKey('token', $query); + $this->assertArrayHasKey('checksum', $query); + $this->assertSame('https://example.com/page', $query['return_url']); + } + + public function test_broker_is_must_redirect_call_returns_false_initially(): void { + $sso = SSO::get_instance(); + + $blog_id = get_current_blog_id(); + $blog = get_blog_details($blog_id); + $date = $blog ? $blog->registered : '2024-01-01 00:00:00'; + $secret = $sso->calculate_secret_from_date($date); + $url = trailingslashit(network_home_url()) . $sso->get_url_path('grant'); + $broker_id = $sso->encode($blog_id, $sso->salt()); + $broker = new SSO_Broker($url, $broker_id, $secret); + $broker = $broker->withTokenIn(new \ArrayObject()); + + $this->assertFalse($broker->is_must_redirect_call()); + } + + // ------------------------------------------------------------------ + // JSONP Content-Type header (the bug fix we are validating) + // ------------------------------------------------------------------ + + /** + * Verify that the JSONP branch in handle_server sets the + * Content-Type: application/javascript header in the source code. + * + * Since handle_server calls exit(), we cannot run it directly in + * a unit test. Instead we verify the source contains the header + * call before the printf to guard against regressions. + */ + public function test_handle_server_source_sets_javascript_content_type_for_jsonp(): void { + $source = file_get_contents( + dirname(__DIR__, 3) . '/inc/sso/class-sso.php' + ); + + // Find the handle_server JSONP branch. + $pattern = "/if\s*\(\s*'jsonp'\s*===\s*\\\$response_type\s*\)\s*\{.*?header\(\s*'Content-Type:\s*application\/javascript/s"; + $this->assertMatchesRegularExpression( + $pattern, + $source, + 'handle_server JSONP branch must set Content-Type: application/javascript header' + ); + } + + /** + * Verify that the JSONP branch in handle_broker also sets the + * Content-Type: application/javascript header. + */ + public function test_handle_broker_source_sets_javascript_content_type_for_jsonp(): void { + $source = file_get_contents( + dirname(__DIR__, 3) . '/inc/sso/class-sso.php' + ); + + // There should be two JSONP blocks with the header — count them. + $count = preg_match_all( + "/header\(\s*'Content-Type:\s*application\/javascript;\s*charset=utf-8'\s*\)/", + $source + ); + + $this->assertSame( + 2, + $count, + 'Both JSONP response paths (handle_server and handle_broker) must set the Content-Type header' + ); + } + + // ------------------------------------------------------------------ + // cache() returns PSR-16 cache + // ------------------------------------------------------------------ + + public function test_cache_returns_psr16_compatible_instance(): void { + $sso = SSO::get_instance(); + $cache = $sso->cache(); + + $this->assertInstanceOf(\Psr\SimpleCache\CacheInterface::class, $cache); + } + + // ------------------------------------------------------------------ + // build_server_request + // ------------------------------------------------------------------ + + public function test_build_server_request_returns_psr7_instance(): void { + $sso = SSO::get_instance(); + $request = $sso->build_server_request('GET'); + + $this->assertInstanceOf(\Psr\Http\Message\ServerRequestInterface::class, $request); + } + + // ------------------------------------------------------------------ + // get_server + // ------------------------------------------------------------------ + + public function test_get_server_returns_server_instance(): void { + $sso = SSO::get_instance(); + $server = $sso->get_server(); + + $this->assertInstanceOf(\Jasny\SSO\Server\Server::class, $server); + } + + // ------------------------------------------------------------------ + // add_additional_origins + // ------------------------------------------------------------------ + + public function test_add_additional_origins_includes_main_site_domain(): void { + global $current_site; + + $sso = SSO::get_instance(); + $origins = $sso->add_additional_origins([]); + + $this->assertContains("http://{$current_site->domain}", $origins); + $this->assertContains("https://{$current_site->domain}", $origins); + } + + public function test_add_additional_origins_preserves_existing_origins(): void { + $sso = SSO::get_instance(); + $origins = $sso->add_additional_origins(['https://existing.com']); + + $this->assertContains('https://existing.com', $origins); + } +} diff --git a/tests/WP_Ultimo/UI/Field_Test.php b/tests/WP_Ultimo/UI/Field_Test.php new file mode 100644 index 000000000..2fe54fe53 --- /dev/null +++ b/tests/WP_Ultimo/UI/Field_Test.php @@ -0,0 +1,258 @@ + 'text', + 'title' => 'Test Field', + ]); + + $atts = $field->get_attributes(); + $this->assertEquals('test_field', $atts['id']); + $this->assertEquals('text', $atts['type']); + $this->assertEquals('Test Field', $atts['title']); + } + + /** + * Test default values are set. + */ + public function test_default_values(): void { + $field = new Field('test_field', []); + + $atts = $field->get_attributes(); + $this->assertEquals('text', $atts['type']); + $this->assertEquals('dashicons-wu-cog', $atts['icon']); + $this->assertFalse($atts['sortable']); + $this->assertEquals(1, $atts['columns']); + } + + /** + * Test set_attribute method. + */ + public function test_set_attribute(): void { + $field = new Field('test_field', ['type' => 'text']); + + $field->set_attribute('title', 'New Title'); + + $atts = $field->get_attributes(); + $this->assertEquals('New Title', $atts['title']); + } + + /** + * Test get_attributes returns array. + */ + public function test_get_attributes(): void { + $field = new Field('test_field', [ + 'type' => 'text', + 'placeholder' => 'Enter text', + ]); + + $atts = $field->get_attributes(); + + $this->assertIsArray($atts); + $this->assertArrayHasKey('id', $atts); + $this->assertArrayHasKey('type', $atts); + $this->assertArrayHasKey('placeholder', $atts); + } + + /** + * Test magic getter returns attribute values. + */ + public function test_magic_getter(): void { + $field = new Field('test_field', [ + 'type' => 'text', + 'title' => 'Test Title', + ]); + + $this->assertEquals('text', $field->type); + $this->assertEquals('Test Title', $field->title); + $this->assertEquals('test_field', $field->id); + } + + /** + * Test get_template_name returns string. + */ + public function test_get_template_name(): void { + $field = new Field('test_field', ['type' => 'text']); + + $template = $field->get_template_name(); + + $this->assertIsString($template); + $this->assertEquals('text', $template); + } + + /** + * Test get_template_name with underscores. + */ + public function test_get_template_name_with_underscores(): void { + $field = new Field('test_field', ['type' => 'multi_checkbox']); + + $template = $field->get_template_name(); + + $this->assertEquals('multi-checkbox', $template); + } + + /** + * Test get_compat_template_name with heading alias. + */ + public function test_get_compat_template_name_heading(): void { + $field = new Field('test_field', ['type' => 'heading']); + + $compat = $field->get_compat_template_name(); + + $this->assertEquals('header', $compat); + } + + /** + * Test get_compat_template_name with select2 alias. + */ + public function test_get_compat_template_name_select2(): void { + $field = new Field('test_field', ['type' => 'select2']); + + $compat = $field->get_compat_template_name(); + + $this->assertEquals('select', $compat); + } + + /** + * Test get_compat_template_name returns false for non-aliased types. + */ + public function test_get_compat_template_name_no_alias(): void { + $field = new Field('test_field', ['type' => 'text']); + + $compat = $field->get_compat_template_name(); + + $this->assertFalse($compat); + } + + /** + * Test field with number type. + */ + public function test_number_field(): void { + $field = new Field('test_field', [ + 'type' => 'number', + 'min' => 0, + 'max' => 100, + ]); + + $this->assertEquals('number', $field->type); + $this->assertEquals(0, $field->min); + $this->assertEquals(100, $field->max); + } + + /** + * Test field with select type and options. + */ + public function test_select_field_with_options(): void { + $options = [ + 'option1' => 'Option 1', + 'option2' => 'Option 2', + ]; + + $field = new Field('test_field', [ + 'type' => 'select', + 'options' => $options, + ]); + + $this->assertEquals('select', $field->type); + $this->assertEquals($options, $field->options); + } + + /** + * Test field with html attributes. + */ + public function test_field_with_html_attr(): void { + $field = new Field('test_field', [ + 'type' => 'text', + 'html_attr' => [ + 'data-custom' => 'value', + 'readonly' => true, + ], + ]); + + $this->assertEquals('value', $field->html_attr['data-custom']); + $this->assertTrue($field->html_attr['readonly']); + } + + /** + * Test field with wrapper classes. + */ + public function test_field_with_wrapper_classes(): void { + $field = new Field('test_field', [ + 'type' => 'text', + 'wrapper_classes' => 'custom-wrapper', + ]); + + $atts = $field->get_attributes(); + $this->assertStringContainsString('custom-wrapper', $atts['wrapper_classes']); + } + + /** + * Test field jsonSerialize. + */ + public function test_json_serialize(): void { + $field = new Field('test_field', [ + 'type' => 'text', + 'title' => 'Test', + ]); + + $json = json_encode($field); + + $this->assertIsString($json); + $this->assertJson($json); + } + + /** + * Test field with required attribute. + */ + public function test_field_with_required(): void { + $field = new Field('test_field', [ + 'type' => 'text', + 'require' => true, + ]); + + $this->assertTrue($field->require); + } + + /** + * Test field with tooltip. + */ + public function test_field_with_tooltip(): void { + $field = new Field('test_field', [ + 'type' => 'text', + 'tooltip' => 'This is a helpful tip', + ]); + + $this->assertEquals('This is a helpful tip', $field->tooltip); + } + + /** + * Test title falls back to name. + */ + public function test_title_fallback_to_name(): void { + $field = new Field('test_field', [ + 'type' => 'text', + 'name' => 'Field Name', + 'title' => false, + ]); + + // Accessing title should fallback to name + $this->assertEquals('Field Name', $field->title); + } +} diff --git a/tests/WP_Ultimo/UI/Form_Test.php b/tests/WP_Ultimo/UI/Form_Test.php new file mode 100644 index 000000000..5b5f53631 --- /dev/null +++ b/tests/WP_Ultimo/UI/Form_Test.php @@ -0,0 +1,277 @@ +get_attributes(); + $this->assertEquals('test_form', $atts['id']); + $this->assertEquals('post', $atts['method']); + } + + /** + * Test constructor with custom attributes. + */ + public function test_constructor_with_custom_attributes(): void { + $form = new Form('test_form', [], [ + 'method' => 'get', + 'title' => 'Test Form', + ]); + + $atts = $form->get_attributes(); + $this->assertEquals('get', $atts['method']); + $this->assertEquals('Test Form', $atts['title']); + } + + /** + * Test default attribute values. + */ + public function test_default_attribute_values(): void { + $form = new Form('test_form', []); + + $atts = $form->get_attributes(); + $this->assertEquals('post', $atts['method']); + $this->assertEquals('', $atts['before']); + $this->assertEquals('', $atts['after']); + $this->assertFalse($atts['action']); + $this->assertFalse($atts['title']); + $this->assertFalse($atts['wrap_in_form_tag']); + $this->assertEquals('div', $atts['wrap_tag']); + $this->assertEquals('settings/fields', $atts['views']); + } + + /** + * Test get_fields returns array. + */ + public function test_get_fields_returns_array(): void { + $form = new Form('test_form', []); + + $fields = $form->get_fields(); + $this->assertIsArray($fields); + } + + /** + * Test get_fields with fields. + */ + public function test_get_fields_with_fields(): void { + $form = new Form('test_form', [ + 'field1' => [ + 'type' => 'text', + 'title' => 'Field 1', + ], + 'field2' => [ + 'type' => 'number', + 'title' => 'Field 2', + ], + ]); + + $fields = $form->get_fields(); + $this->assertCount(2, $fields); + } + + /** + * Test magic getter returns attribute. + */ + public function test_magic_getter(): void { + $form = new Form('test_form', [], [ + 'title' => 'My Form Title', + ]); + + $this->assertEquals('My Form Title', $form->title); + $this->assertEquals('test_form', $form->id); + $this->assertEquals('post', $form->method); + } + + /** + * Test magic getter returns false for missing attribute. + */ + public function test_magic_getter_missing_attribute(): void { + $form = new Form('test_form', []); + + $this->assertFalse($form->nonexistent); + } + + /** + * Test get_attributes returns array. + */ + public function test_get_attributes_returns_array(): void { + $form = new Form('test_form', []); + + $atts = $form->get_attributes(); + $this->assertIsArray($atts); + } + + /** + * Test form with wrap_in_form_tag. + */ + public function test_form_with_wrap_in_form_tag(): void { + $form = new Form('test_form', [], [ + 'wrap_in_form_tag' => true, + ]); + + $atts = $form->get_attributes(); + $this->assertTrue($atts['wrap_in_form_tag']); + } + + /** + * Test form with custom classes. + */ + public function test_form_with_custom_classes(): void { + $form = new Form('test_form', [], [ + 'classes' => 'custom-form-class', + 'field_wrapper_classes' => 'custom-wrapper', + 'field_classes' => 'custom-field', + ]); + + $atts = $form->get_attributes(); + $this->assertEquals('custom-form-class', $atts['classes']); + $this->assertEquals('custom-wrapper', $atts['field_wrapper_classes']); + $this->assertEquals('custom-field', $atts['field_classes']); + } + + /** + * Test form with html_attr. + */ + public function test_form_with_html_attr(): void { + $form = new Form('test_form', [], [ + 'html_attr' => [ + 'class' => 'my-class', + 'data-custom' => 'value', + ], + ]); + + $atts = $form->get_attributes(); + $this->assertIsArray($atts['html_attr']); + $this->assertEquals('my-class', $atts['html_attr']['class']); + $this->assertEquals('value', $atts['html_attr']['data-custom']); + } + + /** + * Test form with action. + */ + public function test_form_with_action(): void { + $form = new Form('test_form', [], [ + 'action' => 'my_action_url', + ]); + + $this->assertEquals('my_action_url', $form->action); + } + + /** + * Test form jsonSerialize. + */ + public function test_json_serialize(): void { + $form = new Form('test_form', [ + 'field1' => ['type' => 'text'], + ], [ + 'title' => 'Test', + ]); + + $json = json_encode($form); + + $this->assertIsString($json); + $this->assertJson($json); + } + + /** + * Test form with variables. + */ + public function test_form_with_variables(): void { + $form = new Form('test_form', [], [ + 'variables' => [ + 'custom_var' => 'custom_value', + ], + ]); + + $atts = $form->get_attributes(); + $this->assertIsArray($atts['variables']); + $this->assertEquals('custom_value', $atts['variables']['custom_var']); + } + + /** + * Test form with step. + */ + public function test_form_with_step(): void { + $form = new Form('test_form', [], [ + 'step' => (object) [ + 'classes' => 'step-class', + 'element_id' => 'step-1', + ], + ]); + + $atts = $form->get_attributes(); + $this->assertEquals('step-class', $atts['step']->classes); + $this->assertEquals('step-1', $atts['step']->element_id); + } + + /** + * Test form with custom views. + */ + public function test_form_with_custom_views(): void { + $form = new Form('test_form', [], [ + 'views' => 'custom/fields', + ]); + + $this->assertEquals('custom/fields', $form->views); + } + + /** + * Test form fields are Field instances. + */ + public function test_form_fields_are_field_instances(): void { + $form = new Form('test_form', [ + 'my_field' => [ + 'type' => 'text', + 'title' => 'My Field', + ], + ]); + + $fields = $form->get_fields(); + $this->assertCount(1, $fields); + + $field = reset($fields); + $this->assertInstanceOf(Field::class, $field); + } + + /** + * Test callable before attribute. + */ + public function test_callable_before_attribute(): void { + $form = new Form('test_form', [], [ + 'before' => function ($form) { + return '
'; + }, + ]); + + $this->assertEquals('
', $form->before); + } + + /** + * Test callable after attribute. + */ + public function test_callable_after_attribute(): void { + $form = new Form('test_form', [], [ + 'after' => function ($form) { + return '
'; + }, + ]); + + $this->assertEquals('
', $form->after); + } +} diff --git a/tests/WP_Ultimo/UI/Simple_Text_Element_Test.php b/tests/WP_Ultimo/UI/Simple_Text_Element_Test.php new file mode 100644 index 000000000..29a5e5001 --- /dev/null +++ b/tests/WP_Ultimo/UI/Simple_Text_Element_Test.php @@ -0,0 +1,174 @@ +element = Simple_Text_Element::get_instance(); + } + + /** + * Test get_instance returns singleton. + */ + public function test_get_instance(): void { + $instance1 = Simple_Text_Element::get_instance(); + $instance2 = Simple_Text_Element::get_instance(); + + $this->assertSame($instance1, $instance2); + } + + /** + * Test element id is simple-text. + */ + public function test_element_id(): void { + $this->assertEquals('simple-text', $this->element->id); + } + + /** + * Test get_title returns string. + */ + public function test_get_title(): void { + $title = $this->element->get_title(); + + $this->assertIsString($title); + $this->assertEquals('Simple Text', $title); + } + + /** + * Test get_description returns string. + */ + public function test_get_description(): void { + $description = $this->element->get_description(); + + $this->assertIsString($description); + $this->assertNotEmpty($description); + } + + /** + * Test get_icon returns string for block context. + */ + public function test_get_icon_block(): void { + $icon = $this->element->get_icon('block'); + + $this->assertIsString($icon); + $this->assertEquals('fa fa-search', $icon); + } + + /** + * Test get_icon returns elementor icon. + */ + public function test_get_icon_elementor(): void { + $icon = $this->element->get_icon('elementor'); + + $this->assertIsString($icon); + $this->assertEquals('eicon-lock-user', $icon); + } + + /** + * Test fields returns array. + */ + public function test_fields(): void { + $fields = $this->element->fields(); + + $this->assertIsArray($fields); + $this->assertArrayHasKey('header', $fields); + $this->assertArrayHasKey('simple_text', $fields); + } + + /** + * Test fields has header field. + */ + public function test_fields_has_header(): void { + $fields = $this->element->fields(); + + $this->assertEquals('header', $fields['header']['type']); + $this->assertEquals('General', $fields['header']['title']); + } + + /** + * Test fields has simple_text field. + */ + public function test_fields_has_simple_text(): void { + $fields = $this->element->fields(); + + $this->assertEquals('textarea', $fields['simple_text']['type']); + $this->assertArrayHasKey('title', $fields['simple_text']); + $this->assertArrayHasKey('placeholder', $fields['simple_text']); + } + + /** + * Test keywords returns array. + */ + public function test_keywords(): void { + $keywords = $this->element->keywords(); + + $this->assertIsArray($keywords); + $this->assertContains('WP Ultimo', $keywords); + $this->assertContains('Ultimate Multisite', $keywords); + $this->assertContains('text', $keywords); + $this->assertContains('simple text', $keywords); + $this->assertContains('shortcode', $keywords); + } + + /** + * Test defaults returns array. + */ + public function test_defaults(): void { + $defaults = $this->element->defaults(); + + $this->assertIsArray($defaults); + $this->assertArrayHasKey('simple_text', $defaults); + } + + /** + * Test defaults has simple_text default. + */ + public function test_defaults_has_simple_text(): void { + $defaults = $this->element->defaults(); + + $this->assertIsString($defaults['simple_text']); + $this->assertNotEmpty($defaults['simple_text']); + } + + /** + * Test element is public. + */ + public function test_element_is_public(): void { + $reflection = new \ReflectionClass($this->element); + $property = $reflection->getProperty('public'); + $property->setAccessible(true); + + $this->assertTrue($property->getValue($this->element)); + } + + /** + * Test element is hidden by default. + */ + public function test_element_hidden_by_default(): void { + $reflection = new \ReflectionClass($this->element); + $property = $reflection->getProperty('hidden_by_default'); + $property->setAccessible(true); + + $this->assertTrue($property->getValue($this->element)); + } +} diff --git a/tests/e2e/cypress/integration/030-modal-form-error-handling.spec.js b/tests/e2e/cypress/integration/030-modal-form-error-handling.spec.js new file mode 100644 index 000000000..e22fd109c --- /dev/null +++ b/tests/e2e/cypress/integration/030-modal-form-error-handling.spec.js @@ -0,0 +1,191 @@ +describe("Modal Form Error Handling", () => { + before(() => { + cy.loginByForm( + Cypress.env("admin").username, + Cypress.env("admin").password + ); + }); + + beforeEach(() => { + cy.loginByForm( + Cypress.env("admin").username, + Cypress.env("admin").password + ); + }); + + /** + * Helper: open the Add Site modal, wait for the form to load, + * then scroll to and click the submit button. + */ + const openSiteModalAndSubmit = () => { + cy.contains("a.wubox", "Add Site").click(); + + cy.get("#WUB_window", { timeout: 10000 }).should("be.visible"); + cy.get("#WUB_ajaxContent .wu_form", { timeout: 10000 }).should("exist"); + + // The submit button may be below the fold in the modal + cy.get('#WUB_ajaxContent button[type="submit"]').scrollIntoView().click(); + }; + + it("Should show an error message when the server returns a 500 on Add Site modal", { + retries: 0, + }, () => { + cy.visit("/wp-admin/network/admin.php?page=wp-ultimo-sites", { + failOnStatusCode: false, + }); + + cy.get("a.wubox", { timeout: 15000 }).should("exist"); + + // Intercept the WU AJAX form handler POST to simulate a 500 error + cy.intercept("POST", "**/\\?wu-ajax=1*action=wu_form_handler*", { + statusCode: 500, + body: "

500 Internal Server Error

", + headers: { "Content-Type": "text/html" }, + }).as("formHandler500"); + + openSiteModalAndSubmit(); + + cy.wait("@formHandler500"); + + // The error message should appear in the modal error app + cy.get('#WUB_ajaxContent [data-wu-app$="_errors"]', { timeout: 10000 }) + .should("contain.text", "An unexpected error occurred"); + + // The form should be unblocked (not stuck loading) + cy.get('#WUB_ajaxContent button[type="submit"]') + .scrollIntoView() + .should("not.be.disabled"); + + // The modal should still be open (not closed silently) + cy.get("#WUB_window").should("be.visible"); + }); + + it("Should show an error message when the server returns invalid JSON", { + retries: 0, + }, () => { + cy.visit("/wp-admin/network/admin.php?page=wp-ultimo-sites", { + failOnStatusCode: false, + }); + + cy.get("a.wubox", { timeout: 15000 }).should("exist"); + + // Return a 200 with non-JSON body (e.g. PHP fatal error output) + cy.intercept("POST", "**/\\?wu-ajax=1*action=wu_form_handler*", { + statusCode: 200, + body: "
\nFatal error: Uncaught Error in /var/www/html/wp-content/plugins/...", + headers: { "Content-Type": "text/html" }, + }).as("formHandlerBadJson"); + + openSiteModalAndSubmit(); + + cy.wait("@formHandlerBadJson"); + + // Should show an error, not spin forever + cy.get('#WUB_ajaxContent [data-wu-app$="_errors"]', { timeout: 10000 }) + .should("contain.text", "An unexpected error occurred"); + + cy.get('#WUB_ajaxContent button[type="submit"]') + .scrollIntoView() + .should("not.be.disabled"); + }); + + it("Should show an error message when the network request fails", { + retries: 0, + }, () => { + cy.visit("/wp-admin/network/admin.php?page=wp-ultimo-sites", { + failOnStatusCode: false, + }); + + cy.get("a.wubox", { timeout: 15000 }).should("exist"); + + // Simulate a network failure + cy.intercept("POST", "**/\\?wu-ajax=1*action=wu_form_handler*", { + forceNetworkError: true, + }).as("formHandlerNetworkError"); + + openSiteModalAndSubmit(); + + // Should show an error for network failure + cy.get('#WUB_ajaxContent [data-wu-app$="_errors"]', { timeout: 15000 }) + .should("contain.text", "An unexpected error occurred"); + + cy.get('#WUB_ajaxContent button[type="submit"]') + .scrollIntoView() + .should("not.be.disabled"); + }); + + it("Should still handle normal validation errors correctly", { + retries: 0, + }, () => { + cy.visit("/wp-admin/network/admin.php?page=wp-ultimo-customers", { + failOnStatusCode: false, + }); + + cy.get("a.wubox", { timeout: 15000 }).should("exist"); + + // Do NOT intercept — let the real server respond with validation errors + cy.contains("a.wubox", "Add Customer").click(); + + cy.get("#WUB_window", { timeout: 10000 }).should("be.visible"); + cy.get("#WUB_ajaxContent .wu_form", { timeout: 10000 }).should("exist"); + + // Submit the form empty to trigger server-side validation + cy.get('#WUB_ajaxContent button[type="submit"]') + .scrollIntoView() + .click(); + + // Validation errors should appear (not our generic server error) + cy.get('#WUB_ajaxContent [data-wu-app$="_errors"]', { timeout: 10000 }) + .should("be.visible"); + + // The error should NOT be the generic server error + cy.get('#WUB_ajaxContent [data-wu-app$="_errors"]') + .should("not.contain.text", "An unexpected error occurred"); + + // The form should remain open and usable + cy.get("#WUB_window").should("be.visible"); + cy.get('#WUB_ajaxContent button[type="submit"]') + .scrollIntoView() + .should("not.be.disabled"); + }); + + it("Should recover and allow resubmission after a server error", { + retries: 0, + }, () => { + cy.visit("/wp-admin/network/admin.php?page=wp-ultimo-sites", { + failOnStatusCode: false, + }); + + cy.get("a.wubox", { timeout: 15000 }).should("exist"); + + let interceptCount = 0; + + // First submission returns 500, second goes through to the real server + cy.intercept("POST", "**/\\?wu-ajax=1*action=wu_form_handler*", (req) => { + interceptCount++; + if (interceptCount === 1) { + req.reply({ + statusCode: 500, + body: "Internal Server Error", + }); + } + // On second call, let it pass through to the real server + }).as("formHandlerRecovery"); + + openSiteModalAndSubmit(); + + // Error message appears after first 500 + cy.get('#WUB_ajaxContent [data-wu-app$="_errors"]', { timeout: 10000 }) + .should("contain.text", "An unexpected error occurred"); + + // Second submit: goes through to the real server + cy.get('#WUB_ajaxContent button[type="submit"]') + .scrollIntoView() + .should("not.be.disabled") + .click(); + + // The server error should be cleared (replaced by real validation errors or success) + cy.get('#WUB_ajaxContent [data-wu-app$="_errors"]', { timeout: 10000 }) + .should("not.contain.text", "An unexpected error occurred"); + }); +}); diff --git a/ultimate-multisite.php b/ultimate-multisite.php index c7ce6053b..4890b0bba 100644 --- a/ultimate-multisite.php +++ b/ultimate-multisite.php @@ -3,7 +3,7 @@ * Plugin Name: Ultimate Multisite – WordPress Multisite SaaS & WaaS Platform * Plugin URI: https://ultimatemultisite.com * Description: Ultimate Multisite is a WordPress Multisite plugin that turns your network into a complete Website-as-a-Service (WaaS) platform with subscriptions, site provisioning, domain mapping, and customer management. Formerly WP Ultimo. - * Version: 2.4.11-beta.4 + * Version: 2.4.11-beta.3 * Author: Ultimate Multisite Community * Author URI: https://ultimatemultisite.com * License: GPLv2 or later