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 );