/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ assertTrue(has_action('wp_head'), 'wp_head hook should have registered actions'); // Check priority is 15 (after theme styles) $wp_head_callbacks = $GLOBALS['wp_filter']['wp_head']->callbacks; $found_css_injection = false; foreach ($wp_head_callbacks as $priority => $callbacks) { foreach ($callbacks as $callback) { if (is_array($callback['function']) && isset($callback['function'][0]) && is_object($callback['function'][0]) && method_exists($callback['function'][0], 'inject_restriction_css')) { $found_css_injection = true; $this->assertEquals(15, $priority, 'CSS injection should have priority 15'); break 2; } } } $this->assertTrue($found_css_injection, 'CSS injection callback should be registered on wp_head'); } /** * Test enhanced CSS generation with caching */ public function test_enhanced_css_generation_with_caching() { // Create test restrictions $this->create_test_doctor_restriction(999, true); $this->create_test_service_restriction(888, 999, true); $integration = $this->plugin->kivicare_integration; // Use reflection to access private method $reflection = new ReflectionClass($integration); $method = $reflection->getMethod('generate_restriction_css'); $method->setAccessible(true); // Clear cache first delete_transient('care_booking_css_' . md5(serialize([[999], [['service_id' => 888, 'doctor_id' => 999]]]))); // First call should generate and cache CSS $start_time = microtime(true); $css1 = $method->invoke($integration, [999], [['service_id' => 888, 'doctor_id' => 999]]); $end_time = microtime(true); $time1 = ($end_time - $start_time) * 1000; // Second call should use cache $start_time = microtime(true); $css2 = $method->invoke($integration, [999], [['service_id' => 888, 'doctor_id' => 999]]); $end_time = microtime(true); $time2 = ($end_time - $start_time) * 1000; $this->assertEquals($css1, $css2, 'Cached CSS should be identical'); $this->assertLessThan($time1, $time2, 'Cached call should be faster'); $this->assertLessThan(10, $time2, 'Cached call should be very fast'); } /** * Test enhanced CSS selectors for KiviCare 3.0+ */ public function test_enhanced_css_selectors() { // Create test restrictions $this->create_test_doctor_restriction(999, true); $this->create_test_service_restriction(888, 999, true); $integration = $this->plugin->kivicare_integration; // Use reflection to access private method $reflection = new ReflectionClass($integration); $method = $reflection->getMethod('generate_restriction_css'); $method->setAccessible(true); $css = $method->invoke($integration, [999], [['service_id' => 888, 'doctor_id' => 999]]); // Check for KiviCare 3.0+ selectors $this->assertStringContainsString('.kc-doctor-item[data-id="999"]', $css, 'Should contain KiviCare 3.0 doctor selector'); $this->assertStringContainsString('.doctor-card[data-doctor="999"]', $css, 'Should contain modern doctor card selector'); $this->assertStringContainsString('.kc-service-item[data-service="888"][data-doctor="999"]', $css, 'Should contain KiviCare 3.0 service selector'); // Check for form selectors $this->assertStringContainsString('select[name=\'doctor_id\'] option[value="999"]', $css, 'Should contain form option selector'); $this->assertStringContainsString('.service-selection[data-doctor="999"] option[value="888"]', $css, 'Should contain contextual service selector'); // Check for booking form selectors $this->assertStringContainsString('.booking-doctor-999', $css, 'Should contain booking doctor selector'); $this->assertStringContainsString('.appointment-service-888.doctor-999', $css, 'Should contain appointment service selector'); } /** * Test CSS chunking for performance */ public function test_css_chunking_performance() { // Create many doctor restrictions $doctor_ids = []; for ($i = 1000; $i <= 1150; $i++) { $this->create_test_doctor_restriction($i, true); $doctor_ids[] = $i; } $integration = $this->plugin->kivicare_integration; // Use reflection to access private method $reflection = new ReflectionClass($integration); $method = $reflection->getMethod('generate_restriction_css'); $method->setAccessible(true); $css = $method->invoke($integration, $doctor_ids, []); // Should contain multiple CSS rules (chunked) $rule_count = substr_count($css, 'display: none !important;'); $this->assertGreaterThan(1, $rule_count, 'Should chunk selectors into multiple CSS rules'); $this->assertLessThan(10, $rule_count, 'Should not create too many rules'); // CSS should be reasonably sized $this->assertLessThan(100000, strlen($css), 'Generated CSS should be reasonably sized'); } /** * Test CSS minification in production */ public function test_css_minification() { // Create test restrictions $this->create_test_doctor_restriction(999, true); $integration = $this->plugin->kivicare_integration; // Use reflection to access private method $reflection = new ReflectionClass($integration); $method = $reflection->getMethod('minify_css'); $method->setAccessible(true); $verbose_css = "/* Comment */\n.test {\n display: none !important;\n visibility: hidden !important;\n}"; $minified = $method->invoke($integration, $verbose_css); $this->assertStringNotContainsString('/*', $minified, 'Comments should be removed'); $this->assertStringNotContainsString("\n", $minified, 'Line breaks should be removed'); $this->assertStringNotContainsString(" ", $minified, 'Multiple spaces should be removed'); $this->assertStringContainsString('display:none!important', $minified, 'Properties should be compressed'); } /** * Test conditional CSS injection based on page content */ public function test_conditional_css_injection() { // Create test restrictions $this->create_test_doctor_restriction(999, true); $integration = $this->plugin->kivicare_integration; // Use reflection to access private method $reflection = new ReflectionClass($integration); $method = $reflection->getMethod('should_inject_css'); $method->setAccessible(true); // Mock KiviCare as active $reflection_active = new ReflectionClass($integration); $active_method = $reflection_active->getMethod('is_kivicare_active'); $active_method->setAccessible(true); // Test with page that should load scripts global $post; $post = (object) ['post_content' => '[kivicare_booking]']; // Should inject CSS $should_inject = $method->invoke($integration); // Note: This might be false if KiviCare is not actually active in test environment // Test with page that shouldn't load scripts $post = (object) ['post_content' => 'Regular page content']; $should_not_inject = $method->invoke($integration); // This test depends on KiviCare being active, so we'll just ensure method doesn't crash $this->assertTrue(is_bool($should_not_inject), 'should_inject_css should return boolean'); } /** * Test graceful degradation CSS classes */ public function test_graceful_degradation_css() { // Create test restrictions $this->create_test_doctor_restriction(999, true); $integration = $this->plugin->kivicare_integration; // Use reflection to access private method $reflection = new ReflectionClass($integration); $method = $reflection->getMethod('generate_restriction_css'); $method->setAccessible(true); $css = $method->invoke($integration, [999], []); // Should contain fallback classes $this->assertStringContainsString('.care-booking-fallback', $css, 'Should contain fallback class'); $this->assertStringContainsString('.care-booking-loading::after', $css, 'Should contain loading class'); $this->assertStringContainsString('opacity: 0.7', $css, 'Should contain fallback styling'); $this->assertStringContainsString('pointer-events: none', $css, 'Should disable pointer events for fallback'); } /** * Test CSS injection with version attribute */ public function test_css_injection_versioning() { // Create test restrictions $this->create_test_doctor_restriction(999, true); // Capture CSS output ob_start(); do_action('wp_head'); $head_output = ob_get_clean(); if (strpos($head_output, 'care-booking-restrictions') !== false) { // Should contain version attribute $this->assertStringContainsString('data-version="' . CARE_BOOKING_BLOCK_VERSION . '"', $head_output, 'Should contain version attribute'); // Should contain proper ID $this->assertStringContainsString('id="care-booking-restrictions"', $head_output, 'Should contain proper style ID'); // Should contain HTML comments for debugging $this->assertStringContainsString('', $head_output, 'Should contain start comment'); $this->assertStringContainsString('', $head_output, 'Should contain end comment'); } } /** * Test error handling in CSS injection */ public function test_css_injection_error_handling() { // Create test restrictions first $this->create_test_doctor_restriction(999, true); // Force an error by mocking a database issue global $wpdb; $original_prefix = $wpdb->prefix; $wpdb->prefix = 'invalid_prefix_'; // Clear cache to force database query delete_transient('care_booking_doctors_blocked'); // CSS injection should handle error gracefully ob_start(); do_action('wp_head'); $head_output = ob_get_clean(); // Restore prefix $wpdb->prefix = $original_prefix; // Should not contain PHP errors $this->assertStringNotContainsString('Fatal error', $head_output, 'Should not contain fatal errors'); $this->assertStringNotContainsString('Warning:', $head_output, 'Should not contain warnings'); // In debug mode, should contain error comment if (defined('WP_DEBUG') && WP_DEBUG) { $this->assertStringContainsString('CSS injection failed', $head_output, 'Should contain error comment in debug mode'); } } /** * Test CSS injection performance */ public function test_css_injection_performance() { // Create moderate number of restrictions for ($i = 1000; $i <= 1050; $i++) { $this->create_test_doctor_restriction($i, true); } for ($i = 2000; $i <= 2025; $i++) { $this->create_test_service_restriction($i, 1000, true); } $start_time = microtime(true); ob_start(); do_action('wp_head'); $head_output = ob_get_clean(); $end_time = microtime(true); $execution_time = ($end_time - $start_time) * 1000; // Should complete quickly (under 200ms) $this->assertLessThan(200, $execution_time, 'CSS injection should be performant'); // Generated CSS should be reasonable size if (strpos($head_output, 'care-booking-restrictions') !== false) { $this->assertLessThan(50000, strlen($head_output), 'Generated CSS should be reasonably sized'); } } /** * Test CSS cache invalidation */ public function test_css_cache_invalidation() { // Create initial restriction $this->create_test_doctor_restriction(999, true); $integration = $this->plugin->kivicare_integration; // Use reflection to access private method $reflection = new ReflectionClass($integration); $method = $reflection->getMethod('generate_restriction_css'); $method->setAccessible(true); // Generate initial CSS $css1 = $method->invoke($integration, [999], []); $this->assertStringContainsString('999', $css1, 'Initial CSS should contain doctor 999'); // Add new restriction $this->create_test_doctor_restriction(998, true); // Cache should be invalidated and new CSS should include both doctors $css2 = $method->invoke($integration, [999, 998], []); $this->assertStringContainsString('999', $css2, 'Updated CSS should contain doctor 999'); $this->assertStringContainsString('998', $css2, 'Updated CSS should contain doctor 998'); $this->assertNotEquals($css1, $css2, 'CSS should be different after adding restriction'); } }