diff --git a/.gitignore b/.gitignore index 955c02cea..999d83b60 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ woocommerce-services.zip dist/ .cache/ tests/e2e-tests/screenshots +tests/docker/docker-compose.override.yml # docker data for local development docker/data diff --git a/classes/class-wc-connect-nux.php b/classes/class-wc-connect-nux.php index 908530907..12d5c69b9 100644 --- a/classes/class-wc-connect-nux.php +++ b/classes/class-wc-connect-nux.php @@ -16,7 +16,8 @@ class WC_Connect_Nux { * Option name for dismissing success banner * after the JP connection flow */ - const SHOULD_SHOW_AFTER_CXN_BANNER = 'should_display_nux_after_jp_cxn_banner'; + const SHOULD_SHOW_AFTER_CXN_BANNER = 'should_display_nux_after_jp_cxn_banner'; + const SHOULD_SHOW_CONTEXTUAL_BANNER = 'should_display_nux_contextual_banner'; /** * @var WC_Connect_Tracks @@ -28,9 +29,34 @@ class WC_Connect_Nux { */ private $shipping_label; - function __construct( WC_Connect_Tracks $tracks, WC_Connect_Shipping_Label $shipping_label ) { - $this->tracks = $tracks; - $this->shipping_label = $shipping_label; + /** + * @var WC_Connect_Service_Settings_Store + */ + protected $service_settings_store; + + /** + * @var WC_Connect_Payment_Methods_Store + */ + protected $payment_methods_store; + + /** + * @var WC_Connect_Service_Schemas_Store + */ + protected $service_schemas_store; + + + function __construct( + WC_Connect_Tracks $tracks, + WC_Connect_Shipping_Label $shipping_label, + WC_Connect_Service_Settings_Store $service_settings_store, + WC_Connect_Payment_Methods_Store $payment_methods_store, + WC_Connect_Service_Schemas_Store $service_schemas_store + ) { + $this->tracks = $tracks; + $this->shipping_label = $shipping_label; + $this->service_settings_store = $service_settings_store; + $this->payment_methods_store = $payment_methods_store; + $this->service_schemas_store = $service_schemas_store; $this->init_pointers(); } @@ -241,23 +267,52 @@ public static function get_banner_type_to_display( $status = array() ) { return 'before_jetpack_connection'; case self::JETPACK_CONNECTED: case self::JETPACK_OFFLINE_MODE: - // Has the user just gone through our NUX connection flow? + $is_us_store = ( isset( $status['store_country'] ) && 'US' === $status['store_country'] ); + + // Priority 1: Standard "after connection" banner (if pending from NUX flow). + // This banner also handles initial TOS acceptance if coming from the NUX connection flow. if ( isset( $status['should_display_after_cxn_banner'] ) && $status['should_display_after_cxn_banner'] ) { return 'after_jetpack_connection'; } - // Has the user already accepted our TOS? Then do nothing. - // Note: TOS is accepted during the after_connection banner - if ( - isset( $status['tos_accepted'] ) - && ! $status['tos_accepted'] - && isset( $status['can_accept_tos'] ) - && $status['can_accept_tos'] - ) { + // Priority 2: TOS acceptance banner (if Jetpack connected, but TOS not yet accepted, + // and the standard "after connection" banner is not pending). + if ( isset( $status['tos_accepted'] ) && ! $status['tos_accepted'] && + isset( $status['can_accept_tos'] ) && $status['can_accept_tos'] ) { return 'tos_only_banner'; } - return false; + // For existing users: if TOS accepted, after_cxn_banner done, but contextual_banner flag not yet set, set it now. + if ( isset( $status['tos_accepted'] ) && $status['tos_accepted'] && + ( ! isset( $status['should_display_after_cxn_banner'] ) || ! $status['should_display_after_cxn_banner'] ) && + ( ! isset( $status['should_display_contextual_banner'] ) || ! $status['should_display_contextual_banner'] ) + ) { + // This user is eligible for contextual banners but the flag isn't set. Set it now. + WC_Connect_Options::update_option( self::SHOULD_SHOW_CONTEXTUAL_BANNER, true ); + // Update the status for the current execution path, so Priority 3 check below can pick it up. + $status['should_display_contextual_banner'] = true; + // Fallback for non-US stores if contextual banner flag is not set + if ( ! $is_us_store ) { + return 'after_cxn_non_us'; + } + } + + // Priority 3: Contextual banners (if standard "after connection" is done or was not applicable, + // TOS is accepted, and the contextual flag is set - either previously or by the block above). + if ( isset( $status['should_display_contextual_banner'] ) && $status['should_display_contextual_banner'] ) { + // Determine which specific contextual banner to show. + if ( $is_us_store ) { + if ( isset( $status['is_wcs_shipping_plugin_active'] ) && ! $status['is_wcs_shipping_plugin_active'] ) { + return 'after_cxn_us_no_wcs_plugin'; + } else { + return 'after_cxn_us_with_wcs_plugin'; + } + } else { + return 'after_cxn_non_us'; + } + } + + return false; // All NUX banners handled or no NUX banner needed for this state. default: return false; } @@ -372,12 +427,22 @@ public function set_up_nux_notices() { // If this is the case, the admin can connect the site on their own, and should be able to use WCS as ususal $jetpack_install_status = $this->get_jetpack_install_status(); + // Ensure is_plugin_active() is available for WCS check + if ( ! function_exists( 'is_plugin_active' ) ) { + include_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + $is_wcs_shipping_plugin_active = is_plugin_active( 'woocommerce-shipping/woocommerce-shipping.php' ); + $store_country = WC()->countries->get_base_country(); + $banner_to_display = self::get_banner_type_to_display( array( - 'jetpack_connection_status' => $jetpack_install_status, - 'tos_accepted' => WC_Connect_Options::get_option( 'tos_accepted' ), - 'can_accept_tos' => WC_Connect_Jetpack::is_current_user_connection_owner() || WC_Connect_Jetpack::is_offline_mode(), - 'should_display_after_cxn_banner' => WC_Connect_Options::get_option( self::SHOULD_SHOW_AFTER_CXN_BANNER ), + 'jetpack_connection_status' => $jetpack_install_status, + 'tos_accepted' => WC_Connect_Options::get_option( 'tos_accepted' ), + 'can_accept_tos' => WC_Connect_Jetpack::is_current_user_connection_owner() || WC_Connect_Jetpack::is_offline_mode(), + 'should_display_after_cxn_banner' => WC_Connect_Options::get_option( self::SHOULD_SHOW_AFTER_CXN_BANNER ), + 'should_display_contextual_banner' => WC_Connect_Options::get_option( self::SHOULD_SHOW_CONTEXTUAL_BANNER ), + 'store_country' => $store_country, + 'is_wcs_shipping_plugin_active' => $is_wcs_shipping_plugin_active, ) ); @@ -391,13 +456,87 @@ public function set_up_nux_notices() { wp_enqueue_style( 'wc_connect_banner' ); add_action( 'admin_notices', array( $this, 'show_banner_before_connection' ), 9 ); break; + case 'after_jetpack_connection': + wp_enqueue_style( 'wc_connect_banner' ); + add_action( 'admin_notices', array( $this, 'show_banner_after_connection' ) ); + break; case 'tos_only_banner': wp_enqueue_style( 'wc_connect_banner' ); add_action( 'admin_notices', array( $this, 'show_tos_banner' ) ); break; - case 'after_jetpack_connection': + case 'after_cxn_us_no_wcs_plugin': + // Enqueue the migration modal assets specifically for this banner on the plugins page. + $plugin_version = WC_Connect_Loader::get_wcs_version(); + // Use the public static method from WC_Connect_Loader + $base_url = WC_Connect_Loader::get_wc_connect_base_url(); // Assuming get_wc_connect_base_url is static + + wp_register_style( 'wcst_wcshipping_migration_admin_notice', $base_url . 'woocommerce-services-wcshipping-migration-admin-notice-' . $plugin_version . '.css', array() ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion + // Add 'wp-element' and 'wc_connect_admin' dependency for React and base script + wp_register_script( 'wcst_wcshipping_migration_admin_notice', $base_url . 'woocommerce-services-wcshipping-migration-admin-notice-' . $plugin_version . '.js', array( 'wc_connect_admin', 'wp-element' ), false, true ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion + + // Localize script data - MATCHING the original register_wcshipping_migration_modal + // Note: The modal primarily uses data-args, localization is minimal here. + wp_localize_script( + 'wcst_wcshipping_migration_admin_notice', + 'wcsPluginData', // Ensure this matches the expected object name in the script + array( + 'assetPath' => $base_url, + 'adminPluginPath' => admin_url( 'plugins.php' ), + ) + ); + + // Enqueue scripts/styles needed for the banner and modal + wp_enqueue_script( 'wc_connect_admin' ); // Ensure base script is loaded first + wp_enqueue_script( 'wcst_wcshipping_migration_admin_notice' ); + wp_enqueue_style( 'wcst_wcshipping_migration_admin_notice' ); wp_enqueue_style( 'wc_connect_banner' ); - add_action( 'admin_notices', array( $this, 'show_banner_after_connection' ) ); + + // Add the action to render the notice and container div + add_action( + 'admin_notices', + function () use ( $banner_to_display ) { + // Instantiate settings classes HERE, inside the closure, using stored dependencies + $account_settings = new WC_Connect_Account_Settings( + $this->service_settings_store, + $this->payment_methods_store + ); + $packages_settings = new WC_Connect_Package_Settings( + $this->service_settings_store, + $this->service_schemas_store + ); + + // Prepare the data for the data-args attribute by calling get() + $container_data_args = array( + 'nonce' => wp_create_nonce( 'wp_rest' ), + 'baseURL' => get_rest_url(), + 'accountSettings' => $account_settings->get(), // Get REAL data + 'packagesSettings' => $packages_settings->get(), // Get REAL data + ); + $encoded_container_args = wp_json_encode( $container_data_args ); + + // Echo the container div needed for the modal React component BEFORE the banner. + // Add 'display: none;' initially; the script should manage visibility. + printf( + '', + esc_attr( $encoded_container_args ) // Use the REAL encoded data + ); + + // Now show the banner itself + $this->show_contextual_after_connection_banner( $banner_to_display ); + } + ); + break; // End case 'after_cxn_us_no_wcs_plugin' + + case 'after_cxn_us_with_wcs_plugin': + case 'after_cxn_non_us': + wp_enqueue_style( 'wc_connect_banner' ); + // Using a closure to correctly pass the argument to the new handler method. + add_action( + 'admin_notices', + function () use ( $banner_to_display ) { + $this->show_contextual_after_connection_banner( $banner_to_display ); + } + ); break; } @@ -405,6 +544,10 @@ public function set_up_nux_notices() { } public function show_banner_before_connection() { + if ( get_option( 'wcs_nux_any_banner_shown', false ) ) { + return; + } + if ( ! $this->should_display_nux_notice_for_current_store_locale() ) { return; } @@ -442,6 +585,10 @@ public function show_banner_before_connection() { } public function show_banner_after_connection() { + if ( get_option( 'wcs_nux_any_banner_shown', false ) ) { + return; + } + if ( ! $this->should_display_nux_notice_for_current_store_locale() ) { return; } @@ -452,8 +599,11 @@ public function show_banner_after_connection() { // Did the user just dismiss? if ( isset( $_GET['wcs-nux-notice'] ) && 'dismiss' === $_GET['wcs-nux-notice'] ) { - // No longer need to keep track of whether the before connection banner was displayed. + // Delete the flag for this banner WC_Connect_Options::delete_option( self::SHOULD_SHOW_AFTER_CXN_BANNER ); + // Set the flag for the next contextual banner + WC_Connect_Options::update_option( self::SHOULD_SHOW_CONTEXTUAL_BANNER, true ); + delete_option( 'wcs_nux_any_banner_shown' ); wp_safe_redirect( remove_query_arg( 'wcs-nux-notice' ) ); exit; } @@ -487,7 +637,95 @@ public function show_banner_after_connection() { ); } + public function show_contextual_after_connection_banner( $banner_type ) { + if ( get_option( 'wcs_nux_any_banner_shown', false ) ) { + return; + } + + $screen = get_current_screen(); + + // This specific banner should only appear on the plugins page. + if ( ! $screen || 'plugins' !== $screen->base ) { + return; + } + + // Still respect the store locale check. + if ( ! $this->should_display_nux_notice_for_current_store_locale() ) { + return; + } + + // Did the user just dismiss? + if ( isset( $_GET['wcs-nux-notice'] ) && 'dismiss' === $_GET['wcs-nux-notice'] ) { + // Delete the flag for this contextual banner + WC_Connect_Options::delete_option( self::SHOULD_SHOW_CONTEXTUAL_BANNER ); + delete_option( 'wcs_nux_any_banner_shown' ); + wp_safe_redirect( remove_query_arg( 'wcs-nux-notice' ) ); + exit; + } + + // By going through the connection process, the user has accepted our TOS + WC_Connect_Options::update_option( 'tos_accepted', true ); + + // Using a generic tracks event, can be made more specific if needed. + $this->tracks->opted_in( 'contextual_connection_banner_viewed' ); + + update_option( 'wcs_nux_any_banner_shown', true ); + + $banner_title = ''; + $banner_description = ''; + $banner_button_text = ''; + $banner_button_link = null; + + update_option( 'wcshipping_migration_state', '0' ); + switch ( $banner_type ) { + case 'after_cxn_us_no_wcs_plugin': + $banner_title = __( 'WooCommerce Shipping & Tax has been renamed to WooCommerce Tax', 'woocommerce-services' ); + $banner_description = __( 'Your tax functionality will continue to work as expected. The shipping functionality in this plugin will be discontinued on September 1, 2025. Please migrate to the new WooCommerce Shipping extension to get discounted labels for UPS, USPS, DHL Express— and more coming soon!', 'woocommerce-services' ); + $banner_button_text = __( 'Try WooCommerce Shipping ', 'woocommerce-services' ); + // Ensure this line uses the special trigger value: + $banner_button_link = '#trigger-migration-modal'; + break; + case 'after_cxn_us_with_wcs_plugin': + $banner_title = __( 'WooCommerce Shipping & Tax has been renamed to WooCommerce Tax', 'woocommerce-services' ); + $banner_description = __( 'Your tax functionality will continue to work as expected. Use WooCommerce Shipping to access deeply discounted UPS, USPS, and DHL shipping labels, reliable shipments, and on-time delivery options.', 'woocommerce-services' ); + $banner_button_text = __( 'Ship with UPS on WooCommerce', 'woocommerce-services' ); + $banner_button_link = 'https://woocommerce.com/document/woocommerce-shipping/#creating-shipping-labels'; + break; + case 'after_cxn_non_us': + $banner_title = __( 'WooCommerce Shipping & Tax has been renamed to WooCommerce Tax', 'woocommerce-services' ); + $banner_description = __( 'Your tax functionality will continue to work as expected. No action is required.', 'woocommerce-services' ); + $banner_button_text = __( 'Close', 'woocommerce-services' ); + $banner_button_link = add_query_arg( + array( + 'wcs-nux-notice' => 'dismiss', + ) + ); + break; + default: + $this->tracks->opted_in( 'contextual_connection_banner_viewed_unknown' ); + return; + } + + $this->show_nux_banner( + array( + 'title' => $banner_title, + 'description' => esc_html( $banner_description ), + 'button_text' => $banner_button_text, + 'button_link' => $banner_button_link, + 'image_url' => plugins_url( + 'images/wcs-notice.png', + __DIR__ + ), + 'should_show_terms' => false, + ) + ); + } + public function show_tos_banner() { + if ( get_option( 'wcs_nux_any_banner_shown', false ) ) { + return; + } + if ( ! $this->should_display_nux_notice_for_current_store_locale() ) { return; } @@ -498,9 +736,13 @@ public function show_tos_banner() { if ( isset( $_GET['wcs-nux-tos'] ) && 'accept' === $_GET['wcs-nux-tos'] ) { WC_Connect_Options::update_option( 'tos_accepted', true ); + // Signal that the contextual banner can now be shown + WC_Connect_Options::update_option( self::SHOULD_SHOW_CONTEXTUAL_BANNER, true ); $this->tracks->opted_in( 'tos_banner' ); + delete_option( 'wcs_nux_any_banner_shown' ); + wp_safe_redirect( remove_query_arg( 'wcs-nux-tos' ) ); exit; } @@ -567,12 +809,23 @@ public function show_nux_banner( $content ) {

- - - + + + + + + + +
@@ -625,6 +878,8 @@ public function register_woocommerce_services_jetpack() { // Make sure we always display the after-connection banner // after the before_connection button is clicked WC_Connect_Options::update_option( self::SHOULD_SHOW_AFTER_CXN_BANNER, true ); + // Ensure the contextual banner flag is not set prematurely + WC_Connect_Options::delete_option( self::SHOULD_SHOW_CONTEXTUAL_BANNER ); WC_Connect_Jetpack::connect_site( $redirect_url ); } diff --git a/classes/class-wc-connect-options.php b/classes/class-wc-connect-options.php index e048f5d77..5396b472f 100644 --- a/classes/class-wc-connect-options.php +++ b/classes/class-wc-connect-options.php @@ -51,6 +51,7 @@ public static function get_option_names( $type = 'compact' ) { 'predefined_packages', 'shipping_methods_migrated', 'should_display_nux_after_jp_cxn_banner', + 'should_display_nux_contextual_banner', 'needs_tax_environment_setup', 'banner_ppec', ); diff --git a/client/components/migration/feature-announcement.jsx b/client/components/migration/feature-announcement.jsx index 4fc56320f..44e035098 100644 --- a/client/components/migration/feature-announcement.jsx +++ b/client/components/migration/feature-announcement.jsx @@ -20,8 +20,9 @@ import { import { installAndActivatePlugins } from './migration-runner'; import { TIME_TO_REMMEMBER_DISMISSAL_SECONDS } from './constants'; -const FeatureAnnouncement = ( { translate, isEligable, previousMigrationState, onClose } ) => { - const [isOpen, setIsOpen] = useState(isEligable); +const FeatureAnnouncement = ( { translate, isEligable, previousMigrationState, onClose, forceOpen = false } ) => { + // Open if either normally eligible OR if forced open by the prop. + const [isOpen, setIsOpen] = useState( forceOpen || isEligable ); const [isUpdating, setIsUpdating] = useState(false); useEffect( () => { diff --git a/client/components/migration/migration-runner.js b/client/components/migration/migration-runner.js index 8c0d1c866..1a3b4cecd 100644 --- a/client/components/migration/migration-runner.js +++ b/client/components/migration/migration-runner.js @@ -228,11 +228,14 @@ const installAndActivatePlugins = async( previousMigrationState ) => { * @returns {string} The next state to run. The name of the state is the key in this object migrationStateTransitions. */ const getNextStateToRun = () => { - if ( ! previousMigrationState ) { - // stateInit + // Check if previousMigrationState is a valid key in the map. + // If not, or if it's falsy, default to the initial state (stateInit). + if ( ! previousMigrationState || ! MIGRATION_ENUM_TO_STATE_NAME_MAP.hasOwnProperty( previousMigrationState ) ) { + // stateInit (key 2) return MIGRATION_ENUM_TO_STATE_NAME_MAP[ 2 ]; } + // If it's a valid key, find the corresponding state name and return its success transition. const currentStateName = MIGRATION_ENUM_TO_STATE_NAME_MAP[ previousMigrationState ]; const nextMigrationState = getMigrationStateByName( currentStateName ); return nextMigrationState.success; // The next state is "success". @@ -258,4 +261,4 @@ const installAndActivatePlugins = async( previousMigrationState ) => { export { installAndActivatePlugins -} \ No newline at end of file +} diff --git a/client/wcshipping-migration-admin-notice.js b/client/wcshipping-migration-admin-notice.js index 15144045e..494f0c095 100644 --- a/client/wcshipping-migration-admin-notice.js +++ b/client/wcshipping-migration-admin-notice.js @@ -25,28 +25,36 @@ const store = createStore(ShippingLabelStore.getReducer(), ShippingLabelStore.ge const wcstWCShippingMigrationNoticeButton = document.getElementById('wcst-wcshipping-migration-notice__click'); const wcstMigrationNoticeDimissButton = document.querySelector('.wcst-wcshipping-migration-notice button.notice-dismiss'); -// Add all button events -["click", "keydown"].forEach(eventName => { - // Clicking "Confirm update" will start the migration. This is the same as popping up the modal and clicking the "Update" button there. - wcstWCShippingMigrationNoticeButton.addEventListener(eventName, (evt) => { - /** - * Prevent form submission when rendered in a form or alike that listens to button click - */ - evt.preventDefault(); - // Pop open feature announcement modal. - ReactDOM.render( - - - , - container - ); +if (wcstWCShippingMigrationNoticeButton) { + // Add all button events + ["click", "keydown"].forEach(eventName => { + // Clicking "Confirm update" will start the migration. This is the same as popping up the modal and clicking the "Update" button there. + wcstWCShippingMigrationNoticeButton.addEventListener(eventName, (evt) => { + /** + * Prevent form submission when rendered in a form or alike that listens to button click + */ + evt.preventDefault(); + // Pop open feature announcement modal, forcing it open regardless of eligibility state. + ReactDOM.render( + + + , + container + ); + }); }); +} - // Dismiss it for 3 days, then remove it from view. - wcstMigrationNoticeDimissButton.addEventListener(eventName, () => { - // window.wpCookies API: wordpress/wp-includes/js/utils.js - window.wpCookies.set('wcst-wcshipping-migration-dismissed', 1, TIME_TO_REMMEMBER_DISMISSAL_SECONDS) - const wcstMigrationAdminNotice = document.querySelector('.wcst-wcshipping-migration-notice'); - wcstMigrationAdminNotice.remove(); +// Dismiss it for 3 days, then remove it from view. +if (wcstMigrationNoticeDimissButton) { + ["click", "keydown"].forEach(eventName => { + wcstMigrationNoticeDimissButton.addEventListener(eventName, () => { + // window.wpCookies API: wordpress/wp-includes/js/utils.js + window.wpCookies.set('wcst-wcshipping-migration-dismissed', 1, TIME_TO_REMMEMBER_DISMISSAL_SECONDS) + const wcstMigrationAdminNotice = document.querySelector('.wcst-wcshipping-migration-notice'); + if (wcstMigrationAdminNotice) { + wcstMigrationAdminNotice.remove(); + } + }); }); -}); +} diff --git a/tests/docker/Dockerfile b/tests/docker/Dockerfile new file mode 100644 index 000000000..8e8fc0ec0 --- /dev/null +++ b/tests/docker/Dockerfile @@ -0,0 +1,44 @@ +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get install -y software-properties-common curl gnupg2 lsb-release + +# Add ondrej/php PPA for PHP 8.2 packages +RUN add-apt-repository ppa:ondrej/php -y \ + && apt-get update + +# Install PHP 8.2 and extensions +RUN apt-get install -y \ + php8.2 \ + php8.2-cli \ + php8.2-mysql \ + php8.2-xml \ + php8.2-mbstring \ + php8.2-curl \ + php8.2-zip \ + php8.2-intl \ + php8.2-bcmath \ + php8.2-simplexml \ + php8.2-tokenizer \ + php8.2-xdebug \ + php-pear \ + git \ + subversion \ + mariadb-client \ + nodejs \ + npm \ + unzip \ + && npm install -g pnpm \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Install composer globally +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +WORKDIR /workspace + +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] +CMD ["bash"] diff --git a/tests/docker/docker-compose.yml b/tests/docker/docker-compose.yml new file mode 100644 index 000000000..3502ee623 --- /dev/null +++ b/tests/docker/docker-compose.yml @@ -0,0 +1,36 @@ +version: "3.8" + +services: + mariadb: + image: mariadb:10.9 + container_name: mariadb_container + environment: + MYSQL_USER: wp_test + MYSQL_PASSWORD: wp_test + MYSQL_DATABASE: wordpress_default + MYSQL_ROOT_PASSWORD: root + ports: + - 3307:3306 + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 3 + + php-tests: + build: . + container_name: php_tests_container + depends_on: + - mariadb + working_dir: /workspace + tty: true + stdin_open: true + volumes: + - ../../:/workspace:cached + environment: + MYSQL_USER: wp_test + MYSQL_PASSWORD: wp_test + MYSQL_DATABASE: wordpress_default + MYSQL_ROOT_PASSWORD: root + WP_VERSION: "6.3.0" # adjust as needed + WC_VERSION: "7.6.1" # adjust as needed diff --git a/tests/docker/entrypoint.sh b/tests/docker/entrypoint.sh new file mode 100644 index 000000000..d8e07d2e2 --- /dev/null +++ b/tests/docker/entrypoint.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -e +exec "$@" diff --git a/tests/docker/run-tests.sh b/tests/docker/run-tests.sh new file mode 100755 index 000000000..838aa1e22 --- /dev/null +++ b/tests/docker/run-tests.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -e + +WC_VERSION=${WC_VERSION:-"7.6.1"} +WP_VERSION=${WP_VERSION:-"6.3.0"} + +echo "Cleaning previous WooCommerce clone..." +if [ -d /tmp/woocommerce ]; then + chmod -R u+w /tmp/woocommerce || true + rm -rf /tmp/woocommerce +fi + +echo "Cloning WooCommerce $WC_VERSION..." +git clone --depth=1 --branch="$WC_VERSION" https://github.com/woocommerce/woocommerce.git /tmp/woocommerce + +echo "Installing WooCommerce dependencies..." +cd /tmp/woocommerce/plugins/woocommerce +composer install +php bin/generate-feature-config.php + +echo "Setting up WordPress test environment..." +mysql -h mariadb -u root -proot -e "DROP DATABASE IF EXISTS wp_test;" +bash tests/bin/install.sh wp_test root root mariadb "${WP_VERSION}" + +if [[ "$WC_VERSION" == "7.5.1" || "$WC_VERSION" == "7.6.1" ]]; then + echo "Installing PHPUnit 8 for legacy WC versions..." + composer require -W phpunit/phpunit:^8 +fi + +echo "Installing plugin dependencies..." +cd /workspace +composer install + +echo "Running PHPUnit tests..." +./vendor/bin/phpunit -c phpunit.xml.dist diff --git a/tests/php/test-class_wc-connect-nux.php b/tests/php/test-class_wc-connect-nux.php index 485259784..052d93ec2 100644 --- a/tests/php/test-class_wc-connect-nux.php +++ b/tests/php/test-class_wc-connect-nux.php @@ -3,7 +3,7 @@ class WP_Test_WC_Connect_NUX extends WC_Unit_Test_Case { public static function set_up_before_class() { - require_once dirname( __FILE__ ) . '/../../classes/class-wc-connect-nux.php'; + require_once __DIR__ . '/../../classes/class-wc-connect-nux.php'; } public function test_get_banner_type_to_display_dev_jp() { @@ -22,13 +22,15 @@ public function test_get_banner_type_to_display_dev_jp() { $this->assertEquals( WC_Connect_Nux::get_banner_type_to_display( array( - 'jetpack_connection_status' => WC_Connect_Nux::JETPACK_OFFLINE_MODE, - 'tos_accepted' => true, - 'can_accept_tos' => null, // irrelevant here, TOS is accepted (DEV) - 'should_display_after_cxn_banner' => false, + 'jetpack_connection_status' => WC_Connect_Nux::JETPACK_OFFLINE_MODE, + 'tos_accepted' => true, + 'can_accept_tos' => null, // irrelevant here, TOS is accepted (DEV) + 'should_display_after_cxn_banner' => false, + 'store_country' => 'JP', + 'should_display_contextual_banner' => true, ) ), - false + 'after_cxn_non_us' ); $this->assertEquals( @@ -158,13 +160,15 @@ public function test_get_banner_type_to_display_with_jp_cxn_with_tos_acceptance( $this->assertEquals( WC_Connect_Nux::get_banner_type_to_display( array( - 'jetpack_connection_status' => WC_Connect_Nux::JETPACK_CONNECTED, - 'tos_accepted' => true, - 'can_accept_tos' => true, - 'should_display_after_cxn_banner' => false, + 'jetpack_connection_status' => WC_Connect_Nux::JETPACK_CONNECTED, + 'tos_accepted' => true, + 'can_accept_tos' => true, + 'should_display_after_cxn_banner' => false, + 'store_country' => 'JP', + 'should_display_contextual_banner' => true, ) ), - false + 'after_cxn_non_us' ); } } diff --git a/woocommerce-services.php b/woocommerce-services.php index aef74b271..cb37532cc 100644 --- a/woocommerce-services.php +++ b/woocommerce-services.php @@ -337,7 +337,7 @@ public static function get_wcs_version() { * * @return string */ - private static function get_wc_connect_base_url() { + public static function get_wc_connect_base_url() { return trailingslashit( defined( 'WOOCOMMERCE_CONNECT_DEV_SERVER_URL' ) ? WOOCOMMERCE_CONNECT_DEV_SERVER_URL : plugins_url( 'dist/', __FILE__ ) ); } @@ -814,7 +814,7 @@ public function load_dependencies() { $payment_methods_store = new WC_Connect_Payment_Methods_Store( $settings_store, $api_client, $logger ); $tracks = new WC_Connect_Tracks( $logger, __FILE__ ); $shipping_label = new WC_Connect_Shipping_Label( $api_client, $settings_store, $schemas_store, $payment_methods_store ); - $nux = new WC_Connect_Nux( $tracks, $shipping_label ); + $nux = new WC_Connect_Nux( $tracks, $shipping_label, $settings_store, $payment_methods_store, $schemas_store ); $taxjar = new WC_Connect_TaxJar_Integration( $api_client, $taxes_logger, $this->wc_connect_base_url ); $paypal_ec = new WC_Connect_PayPal_EC( $api_client, $nux ); $label_reports = new WC_Connect_Label_Reports( $settings_store );