markup (no regression) * - more than one value drops the outer and links each value instead, * because anchors may not nest * - empty rows, and invalid emails, are skipped rather than rendering blanks * - tel: keeps only digits plus a single LEADING plus * * Run: php plugins/a1-tools/tests/contact-info-values.test.php * * @package A1_Tools */ // ── Minimal WordPress surface ─────────────────────────────────────── namespace { define( 'ABSPATH', __DIR__ ); function __( $t, $d = null ) { return $t; } function esc_html( $t ) { return htmlspecialchars( (string) $t, ENT_QUOTES ); } function esc_attr( $t ) { return htmlspecialchars( (string) $t, ENT_QUOTES ); } function esc_url( $u ) { return htmlspecialchars( (string) $u, ENT_QUOTES ); } function sanitize_email( $e ) { return trim( (string) $e ); } function is_email( $e ) { return (bool) filter_var( $e, FILTER_VALIDATE_EMAIL ); } function wp_json_encode( $d ) { return json_encode( $d ); } } // ── Minimal Elementor surface ─────────────────────────────────────── namespace Elementor { class Widget_Base { public $injected = array(); public function get_settings_for_display() { return $this->injected; } } class Repeater { private $c = array(); public function add_control( $id, $a ) { $this->c[ $id ] = $a; } public function get_controls() { return $this->c; } } class Icons_Manager { public static function render_icon( $icon, $attrs = array() ) { echo ''; return true; } } class Controls_Manager { const TEXT = 'text'; const TEXTAREA = 'textarea'; const SWITCHER = 'switcher'; const ICONS = 'icons'; const REPEATER = 'repeater'; const SLIDER = 'slider'; const COLOR = 'color'; const DIMENSIONS = 'dimensions'; const SELECT = 'select'; const TAB_CONTENT = 'content'; const TAB_STYLE = 'style'; } } namespace { require_once __DIR__ . '/../includes/class-a1-tools-contact-info-widget.php'; /** Site variables the widget reads. */ function a1tools_get_site_variables() { return array( 'phone_primary' => '(216) 555-0100', 'email_primary' => 'info@example.com', 'full_address' => '1 Main St, Cleveland, OH 44101', 'operating_hours' => array( 'mon' => '8:00 AM - 10:00 PM', 'sat' => '' ), ); } $failures = 0; function check( $label, $cond ) { global $failures; if ( $cond ) { echo " ok $label\n"; return; } ++$failures; echo " FAIL $label\n"; } /** * Render the widget with the given settings and return its HTML. */ function render_with( array $settings ) { $w = new A1Tools_Contact_Info_Elementor_Widget(); $w->injected = $settings; $render = new ReflectionMethod( $w, 'render' ); $render->setAccessible( true ); ob_start(); $render->invoke( $w ); return ob_get_clean(); } $base = array( 'show_phone' => 'yes', 'phone_link' => 'yes', 'phone_title' => 'Phone', 'phone_icon' => array( 'value' => 'fas fa-phone-alt' ), 'show_email' => '', 'show_location' => '', 'show_hours' => '', 'show_license' => '', ); echo "Contact Info values\n"; // 1. Single linked value → unchanged single-anchor markup. $html = render_with( $base ); check( 'single value keeps one item-level ', 1 === substr_count( $html, ' array( array( 'number' => '+1 (888) 555-0199', 'label' => 'Sales', 'link' => 'yes' ), ), ) ); check( 'multi value drops the item-level anchor', false === strpos( $html, 'a1tools-contact-info-item a1tools-info-box-link' ) ); check( 'multi value links each value', 2 === substr_count( $html, '' ) ); check( 'leading plus is preserved', false !== strpos( $html, 'href="tel:+18885550199"' ) ); check( 'label renders with a colon', false !== strpos( $html, 'Sales:' ) ); check( 'additional value is marked additional', false !== strpos( $html, 'a1tools-info-box-value--additional' ) ); // 3. Empty and unlinked rows. $html = render_with( $base + array( 'phone_extra' => array( array( 'number' => ' ', 'label' => 'Ghost', 'link' => 'yes' ), array( 'number' => '216-555-0200', 'label' => '', 'link' => '' ), ), ) ); check( 'empty row is skipped', false === strpos( $html, 'Ghost' ) ); check( 'unlinked extra renders as plain text', false !== strpos( $html, '216-555-0200' ) ); check( 'unlinked extra has no anchor of its own', 1 === substr_count( $html, ' '', 'show_email' => 'yes', 'email_link' => 'yes', 'email_title' => 'Email', 'email_icon' => array( 'value' => 'fas fa-envelope' ), 'show_location' => '', 'show_hours' => '', 'show_license' => '', 'email_extra' => array( array( 'address' => 'not-an-email', 'label' => 'Bad', 'link' => 'yes' ), array( 'address' => 'sales@example.com', 'label' => 'Sales', 'link' => 'yes' ), ), ) ); check( 'invalid email is dropped', false === strpos( $html, 'not-an-email' ) ); check( 'valid extra email is linked', false !== strpos( $html, 'href="mailto:sales@example.com"' ) ); // 5. Hours rows carry no inline colon (it comes from CSS ::after). $html = render_with( array( 'show_phone' => '', 'show_email' => '', 'show_location' => '', 'show_license' => '', 'show_hours' => 'yes', 'hours_title' => 'Hours', 'hours_icon' => array( 'value' => 'far fa-clock' ), 'hours_closed_text' => 'Closed', ) ); check( 'hours row has day and time spans', false !== strpos( $html, 'Monday8:00 AM - 10:00 PM' ) ); check( 'no colon after the day label', false === strpos( $html, 'Monday:' ) ); check( 'a closed day uses the closed text and class', false !== strpos( $html, 'a1tools-hours-closed' ) && false !== strpos( $html, '>Closed' ) ); // 6. Hours CSS contract (3.3.4). These three were shipped in 3.3.3 and then // explicitly forbidden by the revised spec, so guard the whole block as // source text — a regression here is invisible in a lint and in markup. $init = file_get_contents( __DIR__ . '/../includes/a1-tools-elementor-init.php' ); $hours = ''; if ( preg_match( '/Contact Info hours.*?(?=\n\';)/s', $init, $m ) ) { $hours = $m[0]; } echo "\nHours CSS contract\n"; check( 'hours CSS block was located', '' !== $hours ); check( 'no colon pseudo-element', false === strpos( $hours, '::after' ) && false === strpos( $hours, 'content: ":"' ) ); check( 'no horizontal scrolling fallback', false === strpos( $hours, 'overflow-x' ) ); check( 'rows wrap', false !== strpos( $hours, 'flex-wrap: wrap' ) ); check( 'nowrap only on the time', 1 === substr_count( $hours, 'white-space: nowrap' ) ); check( 'shrink is container-driven', false !== strpos( $hours, '@container a1hours' ) ); check( 'shrink has a media-query fallback', false !== strpos( $hours, '@supports not (container-type: inline-size)' ) ); check( 'shrink floors at 10px', false === strpos( $hours, 'font-size: 0.' ) && substr_count( $hours, 'max(10px,' ) >= 3 ); // The colon must not come back through the markup either. $widget = file_get_contents( __DIR__ . '/../includes/class-a1-tools-contact-info-widget.php' ); check( 'no colon in the hours markup or editor template', false === strpos( $widget, 'a1tools-hours-day">\' . esc_html( $label ) . \':' ) && false === strpos( $widget, '{{ d.label }}:' ) ); echo $failures ? "\n$failures failure(s)\n" : "\nAll passed\n"; exit( $failures ? 1 : 0 ); }