data = array_merge( array( 'id' => 10, 'name' => 'Blue Dress', 'type' => 'simple', 'image_id' => 501, 'children' => array() ), $data ); } public function get_id() { return $this->data['id']; } public function get_name() { return $this->data['name']; } public function get_permalink() { return $this->data['permalink'] ?? 'http://merchant.example/product/blue-dress'; } public function is_type( $type ) { return $type === $this->data['type']; } public function get_image_id() { return $this->data['image_id']; } public function get_children() { return $this->data['children']; } } class WC_Product_Variation extends WC_Product { public function get_attributes() { return $this->data['attributes'] ?? array(); } public function is_in_stock() { return $this->data['stock'] ?? true; } public function is_purchasable() { return $this->data['purchasable'] ?? true; } } function add_action( $hook, $callback, $priority = 10 ) { $GLOBALS['sf_actions'][] = array( $hook, $callback, $priority ); } function add_filter( $hook, $callback, $priority = 10, $args = 1 ) { $GLOBALS['sf_filters'][ $hook ] = $callback; } function add_shortcode( $name, $callback ) { $GLOBALS['sf_actions'][] = array( $name, $callback, 10 ); } function apply_filters( $hook, $value ) { return $value; } function get_option( $name, $default = false ) { return $GLOBALS['sf_options'][ $name ] ?? $default; } function is_product() { return $GLOBALS['sf_product_page']; } function is_admin() { return $GLOBALS['sf_admin']; } function is_preview() { return $GLOBALS['sf_preview']; } function wp_unslash( $value ) { return $value; } function wp_generate_password() { return 'fixed-password'; } function wp_rand() { return 123; } function is_ssl() { return true; } function wc_get_product( $id ) { return $GLOBALS['sf_products'][ $id ] ?? null; } function get_queried_object_id() { return 10; } function get_the_terms( $id, $taxonomy ) { return $GLOBALS['sf_terms'][ $taxonomy ] ?? false; } function sanitize_title( $value ) { return strtolower( trim( preg_replace( '/[^a-z0-9]+/i', '-', $value ), '-' ) ); } function wp_get_attachment_image_url( $id, $size ) { return $id ? "https://merchant.example/media/{$id}-{$size}.jpg" : false; } function wc_placeholder_img_src( $size ) { return "https://merchant.example/placeholder-{$size}.jpg"; } function get_current_user_id() { return $GLOBALS['sf_current_user']; } function home_url() { return 'https://merchant.example'; } function get_bloginfo( $show ) { return 'name' === $show ? ( $GLOBALS['sf_blogname'] ?? 'Merchant Atelier' ) : ''; } function get_locale() { return 'it_IT'; } function esc_url_raw( $url ) { return $url; } function untrailingslashit( $url ) { return rtrim( $url, '/' ); } function wp_register_script() { $GLOBALS['sf_registered'] = true; } function wp_script_is( $handle, $state ) { return $GLOBALS['sf_registered'] && 'registered' === $state; } function wp_enqueue_script( $handle ) { $GLOBALS['sf_enqueued'][] = $handle; } function wp_add_inline_script( $handle, $inline, $position ) { $GLOBALS['sf_inline'][] = array( $handle, $inline, $position ); } function wp_json_encode( $value ) { return json_encode( $value ); } function shortcode_atts( $defaults, $atts ) { return array_merge( $defaults, (array) $atts ); } function __( $text ) { return $text; } function esc_html( $text ) { return htmlspecialchars( (string) $text, ENT_QUOTES ); } function esc_attr( $text ) { return htmlspecialchars( (string) $text, ENT_QUOTES ); } function esc_html_e( $text ) { echo esc_html( $text ); } require_once __DIR__ . '/../includes/class-plugin.php'; require_once __DIR__ . '/../includes/class-status.php'; require_once __DIR__ . '/../includes/class-storefront.php'; use Aisthetix\TryOn\Plugin; use Aisthetix\TryOn\Status; use Aisthetix\TryOn\Storefront; class StorefrontTestStatus extends Status { public $payload; public $allow_tryon = true; public function __construct() { $this->payload = array( 'active' => true, 'withinQuota' => true, 'replay' => array(), 'size' => array( 'hasSizeGuide' => true, 'targeting' => array( 'mode' => 'all' ), 'guides' => array( array( 'categories' => array( 'categories' => array( 'dresses' ) ), 'labels' => array( 'M', 'L' ) ) ), 'fitPreferenceEnabled' => true, 'defaultFitPreference' => 'regular', 'fitOverrides' => array(), ), ); } public function get( $force = false ) { return $this->payload; } public function should_render( $context = array() ) { return $this->allow_tryon; } } $passed = 0; $failed = 0; function sf_check( $name, $condition ) { global $passed, $failed; if ( $condition ) { echo " ✓ {$name}\n"; ++$passed; return; } echo " ✗ {$name}\n"; ++$failed; } function sf_connect( $analytics = true ) { $GLOBALS['sf_options'][ Plugin::OPTION_PUBLISHABLE_KEY ] = 'pk_test'; $GLOBALS['sf_options'][ Plugin::OPTION_SECRET_KEY ] = 'sk_test'; $GLOBALS['sf_options'][ Plugin::OPTION_SETTINGS ] = array( 'enable_analytics' => $analytics ); } echo "Storefront\n"; sf_connect(); $status = new StorefrontTestStatus(); $storefront = new Storefront( $status ); $storefront->init(); sf_check( 'registers storefront hooks and optimizer integrations', count( $GLOBALS['sf_actions'] ) >= 6 && count( $GLOBALS['sf_filters'] ) >= 7 ); sf_check( 'optimizer callbacks retain existing values and add loader markers', in_array( 'bootstrap.js', $GLOBALS['sf_filters']['rocket_delay_js_exclusions']( array( 'keep' ) ), true ) ); sf_check( 'inline optimizer callback adds both config globals', in_array( 'AISTHETIX_WIDGET_URL', $GLOBALS['sf_filters']['rocket_excluded_inline_js_content']( array() ), true ) ); sf_check( 'file optimizer callback adds both widget assets', 2 === count( $GLOBALS['sf_filters']['rocket_exclude_js']( array() ) ) ); foreach ( array( 'litespeed_optm_js_defer_exc', 'litespeed_optimize_js_excludes', 'perfmatters_delay_js_exclusions', 'perfmatters_js_exclusions' ) as $hook ) { sf_check( "{$hook} protects the bootstrap", in_array( 'aisthetix', $GLOBALS['sf_filters'][ $hook ]( array() ), true ) ); } $storefront->register_assets(); sf_check( 'registers the bootstrap and tag filter', true === $GLOBALS['sf_registered'] && isset( $GLOBALS['sf_filters']['script_loader_tag'] ) ); $tag = ''; sf_check( 'leaves unrelated script tags unchanged', $tag === $storefront->add_bootstrap_attributes( $tag, 'other' ) ); $hardened = $storefront->add_bootstrap_attributes( $tag, Storefront::BOOTSTRAP_HANDLE ); sf_check( 'adds optimizer attributes exactly once', 1 === substr_count( $storefront->add_bootstrap_attributes( $hardened, Storefront::BOOTSTRAP_HANDLE ), 'data-cfasync' ) ); $valid_cookie = str_repeat( 'a', 32 ); $_COOKIE = array( Storefront::VISITOR_COOKIE => $valid_cookie ); sf_check( 'reuses a well-formed durable visitor key', $valid_cookie === $storefront->get_visitor_key() && $valid_cookie === $storefront->config_visitor_key() ); $_COOKIE = array( Storefront::VISITOR_COOKIE => '' ); $fresh_storefront = new Storefront( $status ); sf_check( 'mints a strict token for a malformed cookie', 1 === preg_match( '/^[a-f0-9]{32}$/', $fresh_storefront->get_visitor_key() ) ); $ephemeral = new Storefront( $status ); $_COOKIE = array(); sf_check( 'does not advertise an unpersisted visitor token', null === $ephemeral->config_visitor_key() ); $variation = new WC_Product_Variation( array( 'id' => 101, 'attributes' => array( 'pa_size' => 'M', 'color' => 'blue' ), 'image_id' => 701 ) ); $available_large = new WC_Product_Variation( array( 'id' => 104, 'attributes' => array( 'taglia' => 'L' ), 'image_id' => 0 ) ); $sold_out = new WC_Product_Variation( array( 'id' => 102, 'attributes' => array( 'taglia' => 'XL' ), 'image_id' => 0, 'stock' => false ) ); $unknown = new WC_Product_Variation( array( 'id' => 103, 'attributes' => array( 'color' => 'red' ) ) ); $product = new WC_Product( array( 'id' => 10, 'type' => 'variable', 'children' => array( 101, 104, 102, 103, 999 ) ) ); $GLOBALS['sf_products'] = array( 10 => $product, 101 => $variation, 104 => $available_large, 102 => $sold_out, 103 => $unknown ); $GLOBALS['sf_terms']['product_cat'] = array( (object) array( 'term_id' => 9, 'slug' => 'dresses' ), (object) array( 'term_id' => 9, 'slug' => 'dresses' ), (object) array( 'term_id' => 0, 'slug' => 'bad' ) ); $GLOBALS['product'] = $product; $_COOKIE = array( Storefront::VISITOR_COOKIE => $valid_cookie ); $config = $storefront->build_config( $product ); sf_check( 'builds a public-only product config', 'pk_test' === $config['publishableKey'] && ! isset( $config['secretKey'] ) ); sf_check( 'shares a direct HTTPS product permalink', 'https://merchant.example/product/blue-dress' === $config['productUrl'] ); sf_check( 'carries the verified WordPress site name', 'Merchant Atelier' === $config['siteName'] ); sf_check( 'carries stable category ids and slugs', array( 'wc:product_cat:9' ) === $config['productCategoryIds'] && array( 'dresses', 'dresses', 'bad' ) === $config['productCategories'] ); sf_check( 'serializes valid size variations and stock state', 3 === count( $config['productVariants'] ) && false === $config['productVariants'][2]['available'] ); sf_check( 'serializes variation selections and image fallback', 'blue' === $config['variationSelections']['101']['attribute_color'] && false !== strpos( $config['variationImages'][102], '501-large' ) ); sf_check( 'renders a connected eligible product', true === $storefront->can_render() && true === $storefront->can_render_any() && true === $storefront->can_render_size() ); $html = $storefront->render_button( array( 'product' => $product, 'label' => '' ) ); sf_check( 'renders escaped try-on and independent size actions', false !== strpos( $html, '<Try>' ) && false !== strpos( $html, 'data-aisthetix-size-trigger' ) ); sf_check( 'reports collection guide resolution in markup', false !== strpos( $html, 'data-aisthetix-size-guide-resolution="collection"' ) ); sf_check( 'enqueues and injects configuration once', 1 === count( $GLOBALS['sf_enqueued'] ) && 1 === count( $GLOBALS['sf_inline'] ) && false !== strpos( $GLOBALS['sf_inline'][0][1], 'AISTHETIX_WIDGET_CSS' ) ); $status->allow_tryon = false; $size_only = $storefront->render_button( array( 'product' => $product ) ); sf_check( 'keeps the size entry when try-on targeting is off', false === strpos( $size_only, 'id="aisthetix-tryon-trigger"' ) && false !== strpos( $size_only, 'data-aisthetix-size-trigger' ) ); $status->payload['size']['hasSizeGuide'] = false; sf_check( 'returns no markup when both independent entries are ineligible', '' === $storefront->render_button( array( 'product' => $product ) ) ); $grouped = new WC_Product( array( 'type' => 'grouped' ) ); sf_check( 'rejects grouped product ambiguity', '' === $storefront->render_button( array( 'product' => $grouped ) ) ); $GLOBALS['sf_options'][ Plugin::OPTION_SECRET_KEY ] = ''; sf_check( 'rejects explicit products when disconnected', '' === $storefront->render_button( array( 'product' => $product ) ) ); sf_check( 'rejects all page-level gates when disconnected', false === $storefront->can_render() && false === $storefront->can_render_any() && false === $storefront->can_render_size( $product ) ); sf_connect(); $status->payload['size']['hasSizeGuide'] = true; $status->allow_tryon = true; $GLOBALS['sf_options'][ Plugin::OPTION_SETTINGS ]['placement'] = 'manual'; sf_check( 'manual placement suppresses automatic output', '' === ( function () use ( $storefront ) { ob_start(); $storefront->maybe_auto_render(); return ob_get_clean(); } )() ); $shortcode = $storefront->shortcode( array( 'label' => 'Shortcode label' ) ); sf_check( 'shortcode forwards its merchant label', false !== strpos( $shortcode, 'Shortcode label' ) ); $GLOBALS['sf_product_page'] = false; sf_check( 'non-product requests are not eligible', false === $storefront->can_render_any() && false === $storefront->can_render_size() ); $GLOBALS['sf_product_page'] = true; $GLOBALS['product'] = null; $GLOBALS['sf_products'][10] = null; $missing = new Storefront( $status ); sf_check( 'missing resolved product does not render', false === $missing->can_render() && false === $missing->can_render_any() && '' === $missing->render_button() ); sf_check( 'stable category helper rejects invalid inputs', array() === Storefront::stable_category_ids( 'bad' ) ); sf_check( 'guide resolution reports none without a fallback', 'none' === Storefront::size_guide_resolution( array( 'size' => array( 'guides' => array() ) ), array() ) ); $GLOBALS['sf_blogname'] = 'Tom & Jerry's'; $encoded_product = new WC_Product( array( 'name' => 'Tom & Jerry's' ) ); $decoded_config = $storefront->build_config( $encoded_product ); sf_check( 'decodes brand HTML entities', "Tom & Jerry's" === $decoded_config['siteName'] ); sf_check( 'decodes product HTML entities', "Tom & Jerry's" === $decoded_config['productTitle'] ); unset( $GLOBALS['sf_blogname'] ); echo "\n{$passed} passed, {$failed} failed\n"; exit( $failed > 0 ? 1 : 0 );