true, 'edit_posts' => true, 'read' => true ]); // Set default user ID WordPressMock::setUserId(1); // Set common WordPress options WordPressMock::update_option('siteurl', 'https://example.com'); WordPressMock::update_option('home', 'https://example.com'); WordPressMock::update_option('admin_email', 'admin@example.com'); } /** * Setup standard database testing environment * * @return void * @since 1.0.0 */ public static function setupDatabaseEnvironment(): void { DatabaseMock::reset(); // Create standard tables DatabaseMock::createTable('wp_care_booking_restrictions'); DatabaseMock::createTable('kc_appointments'); DatabaseMock::createTable('kc_doctors'); DatabaseMock::createTable('kc_services'); } /** * Setup standard KiviCare testing environment * * @return void * @since 1.0.0 */ public static function setupKiviCareEnvironment(): void { KiviCareMock::reset(); KiviCareMock::setPluginActive(true); KiviCareMock::setupDefaultMockData(); } /** * Setup complete testing environment * * @return void * @since 1.0.0 */ public static function setupCompleteEnvironment(): void { self::setupWordPressEnvironment(); self::setupDatabaseEnvironment(); self::setupKiviCareEnvironment(); } /** * Create sample restriction data for testing * * @param int $count Number of restrictions to create * @return array Created restrictions data * @since 1.0.0 */ public static function createSampleRestrictions(int $count = 10): array { $restrictions = []; $types = ['hide_doctor', 'hide_service', 'hide_combination']; for ($i = 1; $i <= $count; $i++) { $type = $types[($i - 1) % 3]; $restriction = [ 'id' => $i, 'doctor_id' => ($i % 20) + 1, 'service_id' => $type === 'hide_doctor' ? null : (($i % 10) + 1), 'restriction_type' => $type, 'is_active' => ($i % 4) !== 0, // 75% active 'created_at' => date('Y-m-d H:i:s', time() - ($i * 3600)), 'updated_at' => date('Y-m-d H:i:s', time() - ($i * 1800)), 'created_by' => 1, 'metadata' => json_encode(['test' => true, 'order' => $i]) ]; DatabaseMock::insert('wp_care_booking_restrictions', $restriction); $restrictions[] = $restriction; } return $restrictions; } /** * Assert CSS selector matches expected pattern * * @param string $expectedPattern * @param string $actualSelector * @param string $message * @return void * @since 1.0.0 */ public static function assertCssSelectorMatches(string $expectedPattern, string $actualSelector, string $message = ''): void { $pattern = '/^' . str_replace(['[', ']', '"'], ['\[', '\]', '\"'], $expectedPattern) . '$/'; if (!preg_match($pattern, $actualSelector)) { $message = $message ?: "CSS selector '{$actualSelector}' does not match expected pattern '{$expectedPattern}'"; throw new \PHPUnit\Framework\AssertionFailedError($message); } } /** * Assert HTML contains specific data attributes * * @param string $html * @param array $expectedAttributes * @param string $message * @return void * @since 1.0.0 */ public static function assertHtmlContainsDataAttributes(string $html, array $expectedAttributes, string $message = ''): void { foreach ($expectedAttributes as $attribute => $value) { $pattern = '/data-' . preg_quote($attribute, '/') . '=["\']' . preg_quote($value, '/') . '["\']/'; if (!preg_match($pattern, $html)) { $message = $message ?: "HTML does not contain expected data attribute: data-{$attribute}=\"{$value}\""; throw new \PHPUnit\Framework\AssertionFailedError($message); } } } /** * Generate performance test data * * @param int $doctorCount * @param int $serviceCount * @param int $restrictionCount * @return array * @since 1.0.0 */ public static function generatePerformanceTestData(int $doctorCount = 100, int $serviceCount = 50, int $restrictionCount = 500): array { // Add doctors to KiviCare mock for ($i = 1; $i <= $doctorCount; $i++) { KiviCareMock::addMockDoctor($i, [ 'display_name' => "Dr. Test {$i}", 'specialty' => 'Specialty ' . (($i % 10) + 1) ]); } // Add services to KiviCare mock for ($i = 1; $i <= $serviceCount; $i++) { KiviCareMock::addMockService($i, [ 'name' => "Service {$i}", 'duration' => [15, 30, 45, 60][($i % 4)] ]); } // Add restrictions to database for ($i = 1; $i <= $restrictionCount; $i++) { $type = ['hide_doctor', 'hide_service', 'hide_combination'][($i % 3)]; DatabaseMock::insert('wp_care_booking_restrictions', [ 'doctor_id' => ($i % $doctorCount) + 1, 'service_id' => $type === 'hide_doctor' ? null : (($i % $serviceCount) + 1), 'restriction_type' => $type, 'is_active' => ($i % 5) !== 0, // 80% active 'created_at' => date('Y-m-d H:i:s', time() - ($i * 60)), 'created_by' => 1 ]); } return [ 'doctors' => $doctorCount, 'services' => $serviceCount, 'restrictions' => $restrictionCount ]; } /** * Measure execution time of a callback * * @param callable $callback * @return array{result: mixed, time: float} Result and time in milliseconds * @since 1.0.0 */ public static function measureExecutionTime(callable $callback): array { $startTime = microtime(true); $result = $callback(); $endTime = microtime(true); return [ 'result' => $result, 'time' => ($endTime - $startTime) * 1000 // Convert to milliseconds ]; } /** * Assert execution time is within acceptable limits * * @param callable $callback * @param float $maxTimeMs Maximum time in milliseconds * @param string $operation Description of operation being timed * @return mixed Result of the callback * @since 1.0.0 */ public static function assertExecutionTimeWithin(callable $callback, float $maxTimeMs, string $operation = 'Operation'): mixed { $measurement = self::measureExecutionTime($callback); if ($measurement['time'] > $maxTimeMs) { throw new \PHPUnit\Framework\AssertionFailedError( "{$operation} took {$measurement['time']}ms, expected less than {$maxTimeMs}ms" ); } return $measurement['result']; } /** * Create mock AJAX request data * * @param string $action * @param array $data * @return array * @since 1.0.0 */ public static function createMockAjaxRequest(string $action, array $data = []): array { return array_merge([ 'action' => $action, '_ajax_nonce' => WordPressMock::wp_create_nonce($action) ], $data); } /** * Simulate WordPress AJAX response * * @param bool $success * @param mixed $data * @param string $message * @return array * @since 1.0.0 */ public static function createAjaxResponse(bool $success, mixed $data = null, string $message = ''): array { return [ 'success' => $success, 'data' => $data, 'message' => $message ]; } /** * Validate JSON response structure * * @param string $json * @param array $requiredKeys * @return array Decoded JSON data * @throws \PHPUnit\Framework\AssertionFailedError * @since 1.0.0 */ public static function validateJsonResponse(string $json, array $requiredKeys = ['success']): array { $data = json_decode($json, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new \PHPUnit\Framework\AssertionFailedError('Invalid JSON response: ' . json_last_error_msg()); } foreach ($requiredKeys as $key) { if (!array_key_exists($key, $data)) { throw new \PHPUnit\Framework\AssertionFailedError("JSON response missing required key: {$key}"); } } return $data; } /** * Create temporary file for testing * * @param string $content * @param string $extension * @return string Temporary file path * @since 1.0.0 */ public static function createTempFile(string $content = '', string $extension = 'tmp'): string { $tempFile = tempnam(sys_get_temp_dir(), 'care_booking_test_') . '.' . $extension; file_put_contents($tempFile, $content); return $tempFile; } /** * Clean up temporary files created during testing * * @param array $files * @return void * @since 1.0.0 */ public static function cleanupTempFiles(array $files): void { foreach ($files as $file) { if (file_exists($file)) { unlink($file); } } } /** * Assert array has expected structure * * @param array $expectedStructure * @param array $actualArray * @param string $message * @return void * @since 1.0.0 */ public static function assertArrayStructure(array $expectedStructure, array $actualArray, string $message = ''): void { foreach ($expectedStructure as $key => $expectedType) { if (!array_key_exists($key, $actualArray)) { $message = $message ?: "Array missing expected key: {$key}"; throw new \PHPUnit\Framework\AssertionFailedError($message); } if (is_string($expectedType)) { $actualType = gettype($actualArray[$key]); if ($actualType !== $expectedType) { $message = $message ?: "Array key '{$key}' expected type {$expectedType}, got {$actualType}"; throw new \PHPUnit\Framework\AssertionFailedError($message); } } } } /** * Generate random test data * * @param string $type Type of data to generate * @param int $count Number of items to generate * @return array * @since 1.0.0 */ public static function generateRandomTestData(string $type, int $count = 1): array { $data = []; for ($i = 0; $i < $count; $i++) { switch ($type) { case 'doctor': $data[] = [ 'id' => mt_rand(1, 9999), 'display_name' => 'Dr. ' . self::randomString(8), 'specialty' => self::randomString(12), 'email' => strtolower(self::randomString(8)) . '@example.com' ]; break; case 'service': $data[] = [ 'id' => mt_rand(1, 9999), 'name' => self::randomString(15) . ' Service', 'duration' => [15, 30, 45, 60][mt_rand(0, 3)], 'price' => mt_rand(25, 200) . '.00' ]; break; case 'restriction': $types = ['hide_doctor', 'hide_service', 'hide_combination']; $data[] = [ 'doctor_id' => mt_rand(1, 100), 'service_id' => mt_rand(0, 1) ? mt_rand(1, 50) : null, 'restriction_type' => $types[mt_rand(0, 2)], 'is_active' => mt_rand(0, 1) === 1 ]; break; } } return $count === 1 ? $data[0] : $data; } /** * Generate random string * * @param int $length * @return string * @since 1.0.0 */ private static function randomString(int $length): string { $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; $randomString = ''; for ($i = 0; $i < $length; $i++) { $randomString .= $characters[mt_rand(0, strlen($characters) - 1)]; } return $randomString; } }