- Added GitHub spec-kit for development workflow - Standardized file signatures to Descomplicar® format - Updated development configuration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
388 lines
15 KiB
PHP
388 lines
15 KiB
PHP
/**
|
|
* Descomplicar® Crescimento Digital
|
|
* https://descomplicar.pt
|
|
*/
|
|
|
|
<?php
|
|
/**
|
|
* Integration test for CSS injection on wp_head
|
|
*
|
|
* @package CareBookingBlock
|
|
*/
|
|
|
|
/**
|
|
* Test CSS injection functionality on wp_head hook
|
|
*/
|
|
class Test_CSS_Injection extends Care_Booking_Test_Case
|
|
{
|
|
|
|
/**
|
|
* Test wp_head hook is registered for CSS injection
|
|
*/
|
|
public function test_wp_head_hook_registered()
|
|
{
|
|
$this->assertTrue(has_action('wp_head'), 'wp_head hook should have registered actions');
|
|
|
|
// Check if our specific CSS injection hook is registered
|
|
$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(20, $priority, 'CSS injection should have priority 20 (after theme styles)');
|
|
break 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->assertTrue($found_css_injection, 'CSS injection callback should be registered on wp_head');
|
|
}
|
|
|
|
/**
|
|
* Test CSS injection generates correct styles for blocked doctors
|
|
*/
|
|
public function test_css_injection_blocked_doctors()
|
|
{
|
|
// Create test restrictions
|
|
$this->create_test_doctor_restriction(999, true);
|
|
$this->create_test_doctor_restriction(998, true);
|
|
$this->create_test_doctor_restriction(997, false); // Not blocked
|
|
|
|
// Capture CSS output
|
|
ob_start();
|
|
do_action('wp_head');
|
|
$head_output = ob_get_clean();
|
|
|
|
// Should contain CSS for blocked doctors
|
|
$this->assertStringContainsString('.kivicare-doctor[data-doctor-id="999"]', $head_output,
|
|
'Should contain CSS selector for blocked doctor 999');
|
|
$this->assertStringContainsString('.kivicare-doctor[data-doctor-id="998"]', $head_output,
|
|
'Should contain CSS selector for blocked doctor 998');
|
|
$this->assertStringNotContainsString('.kivicare-doctor[data-doctor-id="997"]', $head_output,
|
|
'Should NOT contain CSS selector for non-blocked doctor 997');
|
|
|
|
// Should contain display: none directive
|
|
$this->assertStringContainsString('display: none !important;', $head_output,
|
|
'Should contain display: none !important directive');
|
|
|
|
// Should be wrapped in style tags with proper data attribute
|
|
$this->assertStringContainsString('<style data-care-booking>', $head_output,
|
|
'Should contain opening style tag with data attribute');
|
|
$this->assertStringContainsString('</style>', $head_output,
|
|
'Should contain closing style tag');
|
|
}
|
|
|
|
/**
|
|
* Test CSS injection generates correct styles for blocked services
|
|
*/
|
|
public function test_css_injection_blocked_services()
|
|
{
|
|
// Create test service restrictions
|
|
$this->create_test_service_restriction(888, 999, true); // Block service 888 for doctor 999
|
|
$this->create_test_service_restriction(887, 998, true); // Block service 887 for doctor 998
|
|
$this->create_test_service_restriction(886, 999, false); // Don't block service 886 for doctor 999
|
|
|
|
// Capture CSS output
|
|
ob_start();
|
|
do_action('wp_head');
|
|
$head_output = ob_get_clean();
|
|
|
|
// Should contain CSS for blocked services with doctor context
|
|
$this->assertStringContainsString('.kivicare-service[data-service-id="888"][data-doctor-id="999"]', $head_output,
|
|
'Should contain CSS selector for service 888 blocked for doctor 999');
|
|
$this->assertStringContainsString('.kivicare-service[data-service-id="887"][data-doctor-id="998"]', $head_output,
|
|
'Should contain CSS selector for service 887 blocked for doctor 998');
|
|
|
|
// Should NOT contain CSS for non-blocked service
|
|
$this->assertStringNotContainsString('[data-service-id="886"]', $head_output,
|
|
'Should NOT contain CSS selector for non-blocked service 886');
|
|
|
|
// Should contain display: none directive
|
|
$this->assertStringContainsString('display: none !important;', $head_output);
|
|
}
|
|
|
|
/**
|
|
* Test CSS injection includes fallback selectors
|
|
*/
|
|
public function test_css_injection_fallback_selectors()
|
|
{
|
|
// Create test restrictions
|
|
$this->create_test_doctor_restriction(999, true);
|
|
$this->create_test_service_restriction(888, 999, true);
|
|
|
|
// Capture CSS output
|
|
ob_start();
|
|
do_action('wp_head');
|
|
$head_output = ob_get_clean();
|
|
|
|
// Should include fallback ID selectors
|
|
$this->assertStringContainsString('#doctor-999', $head_output,
|
|
'Should include fallback ID selector for doctor');
|
|
$this->assertStringContainsString('#service-888-doctor-999', $head_output,
|
|
'Should include fallback ID selector for service');
|
|
|
|
// Should include fallback option selectors
|
|
$this->assertStringContainsString('.doctor-selection option[value="999"]', $head_output,
|
|
'Should include fallback option selector for doctor');
|
|
$this->assertStringContainsString('.service-selection option[value="888"]', $head_output,
|
|
'Should include fallback option selector for service');
|
|
}
|
|
|
|
/**
|
|
* Test CSS injection handles empty restrictions
|
|
*/
|
|
public function test_css_injection_empty_restrictions()
|
|
{
|
|
// No restrictions created
|
|
|
|
// Capture CSS output
|
|
ob_start();
|
|
do_action('wp_head');
|
|
$head_output = ob_get_clean();
|
|
|
|
// Should still output style tags but with minimal content
|
|
if (strpos($head_output, '<style data-care-booking>') !== false) {
|
|
$this->assertStringContainsString('<style data-care-booking>', $head_output);
|
|
$this->assertStringContainsString('</style>', $head_output);
|
|
|
|
// Content should be minimal (just comments or empty)
|
|
$style_content = $this->extract_style_content($head_output);
|
|
$this->assertLessThan(100, strlen(trim($style_content)),
|
|
'Style content should be minimal when no restrictions exist');
|
|
} else {
|
|
// Or no style output at all is also acceptable
|
|
$this->assertStringNotContainsString('data-care-booking', $head_output,
|
|
'No CSS should be output when no restrictions exist');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test CSS injection uses cache for performance
|
|
*/
|
|
public function test_css_injection_uses_cache()
|
|
{
|
|
// Create test restrictions
|
|
$this->create_test_doctor_restriction(999, true);
|
|
|
|
// Pre-populate cache
|
|
$blocked_doctors = [999];
|
|
$blocked_services = [];
|
|
set_transient('care_booking_doctors_blocked', $blocked_doctors, 3600);
|
|
|
|
// Measure performance with cache
|
|
$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 be very fast with cache (under 50ms)
|
|
$this->assertLessThan(50, $execution_time, 'CSS injection should be fast with cache');
|
|
|
|
// Should contain correct CSS
|
|
$this->assertStringContainsString('.kivicare-doctor[data-doctor-id="999"]', $head_output);
|
|
}
|
|
|
|
/**
|
|
* Test CSS injection handles database errors gracefully
|
|
*/
|
|
public function test_css_injection_handles_database_errors()
|
|
{
|
|
// Create test restrictions first
|
|
$this->create_test_doctor_restriction(999, true);
|
|
|
|
// Mock database error
|
|
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 throw fatal errors
|
|
$this->assertTrue(true, 'CSS injection should handle database errors without fatal errors');
|
|
|
|
// May contain minimal or no CSS output due to error
|
|
if (strpos($head_output, '<style') !== false) {
|
|
$this->assertStringContainsString('<style', $head_output, 'Should contain style tags even on error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test CSS injection output is properly escaped and secure
|
|
*/
|
|
public function test_css_injection_security()
|
|
{
|
|
// Create test restrictions with edge case IDs
|
|
$this->create_test_doctor_restriction(999, true);
|
|
|
|
// Capture CSS output
|
|
ob_start();
|
|
do_action('wp_head');
|
|
$head_output = ob_get_clean();
|
|
|
|
// Should not contain any unescaped content
|
|
$this->assertStringNotContainsString('<script', $head_output, 'Should not contain script tags');
|
|
$this->assertStringNotContainsString('javascript:', $head_output, 'Should not contain javascript: protocol');
|
|
$this->assertStringNotContainsString('expression(', $head_output, 'Should not contain CSS expressions');
|
|
|
|
// Should contain proper CSS syntax
|
|
$this->assertRegExp('/\{[^}]*display:\s*none\s*!important[^}]*\}/', $head_output,
|
|
'Should contain proper CSS syntax for display:none');
|
|
}
|
|
|
|
/**
|
|
* Test CSS injection only occurs on frontend pages
|
|
*/
|
|
public function test_css_injection_frontend_only()
|
|
{
|
|
$this->create_test_doctor_restriction(999, true);
|
|
|
|
// Test admin context
|
|
set_current_screen('edit-post');
|
|
|
|
ob_start();
|
|
do_action('wp_head');
|
|
$admin_output = ob_get_clean();
|
|
|
|
// Test frontend context
|
|
set_current_screen('front');
|
|
|
|
ob_start();
|
|
do_action('wp_head');
|
|
$frontend_output = ob_get_clean();
|
|
|
|
// CSS should be injected on frontend but policy may vary for admin
|
|
// At minimum, it should work on frontend
|
|
if (strpos($frontend_output, '<style data-care-booking>') !== false) {
|
|
$this->assertStringContainsString('.kivicare-doctor[data-doctor-id="999"]', $frontend_output,
|
|
'CSS should be injected on frontend');
|
|
}
|
|
|
|
// Admin behavior may vary based on implementation
|
|
$this->assertTrue(true, 'CSS injection should handle admin vs frontend context appropriately');
|
|
}
|
|
|
|
/**
|
|
* Test CSS injection performance with large restriction sets
|
|
*/
|
|
public function test_css_injection_performance_large_dataset()
|
|
{
|
|
// Create many restrictions
|
|
for ($i = 1000; $i <= 1100; $i++) {
|
|
$this->create_test_doctor_restriction($i, true);
|
|
}
|
|
|
|
for ($i = 2000; $i <= 2050; $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 in reasonable time even with large datasets (under 200ms)
|
|
$this->assertLessThan(200, $execution_time, 'CSS injection should handle large datasets efficiently');
|
|
|
|
// Should contain CSS for many restrictions
|
|
$this->assertStringContainsString('.kivicare-doctor[data-doctor-id="1000"]', $head_output);
|
|
$this->assertStringContainsString('.kivicare-service[data-service-id="2000"]', $head_output);
|
|
|
|
// CSS should be reasonably sized (under 100KB)
|
|
$this->assertLessThan(100000, strlen($head_output), 'Generated CSS should be reasonably sized');
|
|
}
|
|
|
|
/**
|
|
* Test CSS injection minification and optimization
|
|
*/
|
|
public function test_css_injection_optimization()
|
|
{
|
|
// Create test restrictions
|
|
$this->create_test_doctor_restriction(999, true);
|
|
$this->create_test_doctor_restriction(998, true);
|
|
$this->create_test_service_restriction(888, 999, true);
|
|
|
|
ob_start();
|
|
do_action('wp_head');
|
|
$head_output = ob_get_clean();
|
|
|
|
$style_content = $this->extract_style_content($head_output);
|
|
|
|
// Should combine selectors efficiently
|
|
$doctor_selectors = substr_count($style_content, '.kivicare-doctor');
|
|
$this->assertGreaterThan(0, $doctor_selectors, 'Should contain doctor selectors');
|
|
|
|
// Should minimize redundant CSS
|
|
$display_none_count = substr_count($style_content, 'display: none !important');
|
|
$this->assertGreaterThan(0, $display_none_count, 'Should contain display:none declarations');
|
|
|
|
// Should not contain excessive whitespace if minified
|
|
if (strpos($style_content, ' ') === false) {
|
|
$this->assertTrue(true, 'CSS appears to be minified');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test CSS injection cache invalidation
|
|
*/
|
|
public function test_css_injection_cache_invalidation()
|
|
{
|
|
// Create initial restriction
|
|
$this->create_test_doctor_restriction(999, true);
|
|
|
|
// Generate initial CSS
|
|
ob_start();
|
|
do_action('wp_head');
|
|
$initial_output = ob_get_clean();
|
|
|
|
$this->assertStringContainsString('data-doctor-id="999"', $initial_output);
|
|
|
|
// Add new restriction (should invalidate cache)
|
|
$this->create_test_doctor_restriction(998, true);
|
|
|
|
// Simulate cache invalidation
|
|
delete_transient('care_booking_doctors_blocked');
|
|
delete_transient('care_booking_restrictions_hash');
|
|
|
|
// Generate CSS again
|
|
ob_start();
|
|
do_action('wp_head');
|
|
$updated_output = ob_get_clean();
|
|
|
|
// Should now include both doctors
|
|
$this->assertStringContainsString('data-doctor-id="999"', $updated_output);
|
|
$this->assertStringContainsString('data-doctor-id="998"', $updated_output);
|
|
}
|
|
|
|
/**
|
|
* Helper method to extract style content from HTML
|
|
*/
|
|
private function extract_style_content($html)
|
|
{
|
|
if (preg_match('/<style[^>]*data-care-booking[^>]*>(.*?)<\/style>/s', $html, $matches)) {
|
|
return $matches[1];
|
|
}
|
|
return '';
|
|
}
|
|
} |