utilikit-1.0.0/tests/src/Traits/UtilikitTestHelpers.php
tests/src/Traits/UtilikitTestHelpers.php
<?php
declare(strict_types=1);
namespace Drupal\Tests\utilikit\Traits;
use PHPUnit\Framework\MockObject\MockObject;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Entity\EntityInterface;
/**
* @file
* Helper functions and utilities for UtiliKit tests.
*
* Provides comprehensive testing utilities for UtiliKit module including
* entity creation with utility classes, CSS validation, rate limiting tests,
* and mock configuration helpers. These utilities support unit tests,
* kernel tests, and functional tests across the UtiliKit testing suite.
*/
/**
* Provides helper methods for UtiliKit tests.
*
* This trait contains common testing utilities used across UtiliKit test
* classes. It includes methods for creating test entities with utility
* classes, generating HTML content, validating CSS output, testing rate
* limits, and creating mock configurations.
*
* Usage:
* @code
* class MyUtilikitTest extends UnitTestCase {
* use UtilikitTestHelpers;
*
* public function testSomething() {
* $classes = $this->getTestUtilityClasses('responsive');
* $entity = $this->createEntityWithUtilityClasses('node', [], $classes);
* // ... test logic
* }
* }
* @endcode
*/
trait UtilikitTestHelpers {
/**
* Creates a sample entity with UtiliKit classes for testing.
*
* This method creates an entity of the specified type and populates it
* with HTML content containing UtiliKit utility classes. Useful for
* testing class detection, CSS generation, and entity processing workflows.
*
* @param string $entity_type
* The entity type to create (e.g., 'node', 'block_content').
* @param array $values
* Base values for the entity. If 'body' is not provided, it will be
* automatically generated with utility classes.
* @param array $utility_classes
* Array of utility classes to include in the entity's body field.
* These should be valid UtiliKit class names.
*
* @return \Drupal\Core\Entity\EntityInterface
* The created and saved entity with utility classes.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* Thrown when the entity type definition is invalid.
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* Thrown when the entity type is not found.
* @throws \Drupal\Core\Entity\EntityStorageException
* Thrown when the entity cannot be saved.
*
* @example
* ```php
* // Create a node with responsive utility classes
* $classes = ['uk-pd--20', 'uk-md-pd--30', 'uk-lg-pd--40'];
* $entity = $this->createEntityWithUtilityClasses('node', [
* 'type' => 'article',
* 'title' => 'Test Article',
* ], $classes);
* ```
*/
protected function createEntityWithUtilityClasses(string $entity_type, array $values, array $utility_classes): EntityInterface {
// Validate utility classes before creating entity.
$validated_classes = $this->validateUtilityClasses($utility_classes);
$storage = \Drupal::entityTypeManager()->getStorage($entity_type);
// Add utility classes to a body field if not already provided.
if (!isset($values['body'])) {
$values['body'] = [
'value' => $this->generateHtmlWithClasses($validated_classes),
'format' => 'full_html',
];
}
$entity = $storage->create($values);
$entity->save();
return $entity;
}
/**
* Generates HTML content with UtiliKit classes for testing.
*
* Creates a structured HTML string with each utility class applied to
* individual div elements. This is useful for testing class detection
* algorithms and CSS generation processes.
*
* @param array $classes
* Array of utility class names to include in the HTML. Each class
* will be applied to a separate div element with the 'utilikit' base
* class for proper framework detection.
*
* @return string
* HTML string with properly formatted utility class elements wrapped
* in a test container div.
*
* @example
* ```php
* $classes = ['uk-pd--20', 'uk-bg--ff0000'];
* $html = $this->generateHtmlWithClasses($classes);
* // Returns: '<div class="test-wrapper">...</div>'
* ```
*/
protected function generateHtmlWithClasses(array $classes): string {
$html = '<div class="test-wrapper">';
foreach ($classes as $class) {
$escaped_class = htmlspecialchars($class, ENT_QUOTES, 'UTF-8');
$html .= sprintf(
'<div class="utilikit %s">Test content with %s</div>',
$escaped_class,
$escaped_class
);
}
$html .= '</div>';
return $html;
}
/**
* Asserts that CSS contains expected rules for utility classes.
*
* Validates that generated CSS includes proper rules for the specified
* utility classes. Uses regular expressions to match CSS class selectors
* and their corresponding rule blocks.
*
* @param string $css
* The CSS string to validate. Should contain generated UtiliKit styles.
* @param array $expected_classes
* Array of class names that should have corresponding CSS rules.
* Class names should not include the leading dot.
*
* @throws \PHPUnit\Framework\AssertionFailedError
* Thrown when expected CSS rules are not found.
*
* @example
* ```php
* $css = '.uk-pd--20 { padding: 20px; } .uk-mg--10 { margin: 10px; }';
* $classes = ['uk-pd--20', 'uk-mg--10'];
* $this->assertCssContainsClasses($css, $classes);
* ```
*/
protected function assertCssContainsClasses(string $css, array $expected_classes): void {
foreach ($expected_classes as $class) {
// Validate class format before testing.
if (!$this->isValidUtilityClass($class)) {
$this->fail("Invalid utility class format: $class");
}
$escaped_class = preg_quote($class, '/');
$this->assertMatchesRegularExpression(
'/\.' . $escaped_class . '\s*\{[^}]+\}/',
$css,
"CSS should contain rules for class: $class"
);
}
}
/**
* Gets predefined utility classes for various test scenarios.
*
* Returns arrays of utility classes tailored for specific testing
* scenarios. This centralizes test data management and ensures
* consistency across different test methods and classes.
*
* Available scenarios:
* - 'basic': Simple padding, margin, background, and text color classes
* - 'responsive': Classes with breakpoint prefixes
* - 'complex': Advanced layout classes with multiple values
* - 'grid': CSS Grid-specific utility classes
* - 'invalid': Invalid class names for error testing
* - 'performance': Large array for performance testing (1000 classes)
* - 'transforms': Transform and positioning utilities
* - 'colors': Color-related utility classes
* - 'typography': Text and font utility classes
*
* @param string $scenario
* The test scenario name. Defaults to 'basic' if not recognized.
*
* @return array
* Array of utility class names appropriate for the scenario.
*
* @example
* ```php
* // Get responsive classes for breakpoint testing
* $responsive_classes = $this->getTestUtilityClasses('responsive');
*
* // Get performance test classes
* $performance_classes = $this->getTestUtilityClasses('performance');
* ```
*/
protected function getTestUtilityClasses(string $scenario = 'basic'): array {
$scenarios = [
'basic' => [
'uk-pd--20',
'uk-mg--t-10',
'uk-bg--ff0000',
'uk-tc--333333',
],
'responsive' => [
'uk-pd--20',
'uk-md-pd--30',
'uk-lg-pd--40',
'uk-xl-pd--50',
],
'complex' => [
'uk-pd--10-20-30-40',
'uk-br--t-10',
'uk-dp--flex',
'uk-jc--center',
'uk-ai--center',
'uk-gp--16',
],
'grid' => [
'uk-dp--grid',
'uk-gc--repeat-3-1fr',
'uk-gr--100px-auto-1fr',
'uk-gl--1-3',
'uk-gw--2-4',
],
'transforms' => [
'uk-rt--45',
'uk-sc--150',
'uk-tx--50px',
'uk-ty--100px',
],
'colors' => [
'uk-bg--ff0000',
'uk-tc--000000',
'uk-bc--00ff00',
'uk-bg--rgba-255-255-255-0d5',
],
'typography' => [
'uk-fs--16px',
'uk-fw--bold',
'uk-lh--1d5',
'uk-ta--center',
],
'invalid' => [
'uk-invalid--class',
'not-a-utility-class',
'uk-pd--invalid-value',
'uk-zz--20',
],
'performance' => array_map(
fn($i) => "uk-pd--{$i}",
range(1, 1000)
),
];
return $scenarios[$scenario] ?? $scenarios['basic'];
}
/**
* Creates a mock configuration object for testing.
*
* Generates a PHPUnit mock of ImmutableConfig that returns predefined
* values for configuration keys. Useful for testing services and
* functions that depend on configuration without requiring full
* configuration setup.
*
* @param array $settings
* Associative array of configuration key-value pairs. The mock will
* return these values when get() is called with the corresponding key.
*
* @return \PHPUnit\Framework\MockObject\MockObject
* A mock configuration object that implements the ImmutableConfig
* interface and returns the provided settings when get() is called.
*
* @example
* ```php
* // Create mock config with specific settings
* $config = $this->createMockConfig([
* 'rendering_mode' => 'static',
* 'dev_mode' => true,
* 'scope' => 'global',
* ]);
*
* // Use in service constructor or method calls
* $service = new SomeService($config);
* ```
*/
protected function createMockConfig(array $settings): MockObject {
$config = $this->createMock(ImmutableConfig::class);
$config->method('get')
->willReturnCallback(function ($key) use ($settings) {
return $settings[$key] ?? NULL;
});
return $config;
}
/**
* Asserts that a rate limit is properly enforced.
*
* Tests rate limiting functionality by repeatedly calling an action
* and verifying that it stops accepting requests after the limit is
* reached. Useful for testing API endpoints, file generation, and
* other rate-limited operations.
*
* @param callable $action
* The action to test. Should return FALSE or throw an exception
* when rate limited, or return a response object with status 429.
* @param int $limit
* The expected rate limit (maximum allowed calls).
* @param int $window
* The time window in seconds (used for assertion messaging).
*
* @throws \PHPUnit\Framework\AssertionFailedError
* Thrown when rate limiting is not properly enforced.
*
* @example
* ```php
* // Test API rate limiting
* $this->assertRateLimitEnforced(
* function() { return $this->apiController->generateCss(); },
* 5, // 5 requests per window
* 60 // 60 second window
* );
* ```
*/
protected function assertRateLimitEnforced(callable $action, int $limit, int $window): void {
$count = 0;
// Attempt to exceed the rate limit by calling action multiple times.
for ($i = 0; $i < $limit + 5; $i++) {
try {
$result = $action();
// Check for rate limit indicators.
if ($result === FALSE || (is_object($result) && method_exists($result, 'getStatusCode') && $result->getStatusCode() === 429)) {
// Rate limit hit - stop counting.
break;
}
$count++;
}
catch (\Exception $e) {
// Rate limit might throw exception - stop counting.
break;
}
}
$this->assertLessThanOrEqual(
$limit,
$count,
"Rate limit of $limit per $window seconds should be enforced"
);
}
/**
* Waits for AJAX requests to complete in functional tests.
*
* Pauses test execution until all jQuery AJAX requests have finished.
* Essential for functional tests that interact with dynamic content
* or AJAX-powered interfaces.
*
* @param int $timeout
* Maximum time to wait in seconds before giving up. Defaults to 10.
*
* @throws \Exception
* Thrown when AJAX requests don't complete within the timeout period.
*
* @example
* ```php
* // Click button that triggers AJAX request
* $this->getSession()->getPage()->clickLink('Update CSS');
*
* // Wait for AJAX to complete
* $this->waitForAjax(15);
*
* // Continue with assertions
* $this->assertSession()->pageTextContains('CSS updated successfully');
* ```
*/
protected function waitForAjax(int $timeout = 10): void {
$this->getSession()->wait(
$timeout * 1000,
'(typeof jQuery !== "undefined" && jQuery.active === 0)'
);
}
/**
* Validates utility class names according to UtiliKit naming conventions.
*
* Checks if utility class names follow the expected pattern and filters
* out invalid classes. This method helps ensure test data integrity and
* prevents testing with malformed class names.
*
* @param array $classes
* Array of utility class names to validate.
*
* @return array
* An array of valid utility class names with invalid ones filtered out.
* Only classes matching the UtiliKit naming pattern are returned.
*
* @example
* ```php
* $classes = ['uk-pd--20', 'invalid-class', 'uk-mg--10'];
* $valid = $this->validateUtilityClasses($classes);
* // Returns: ['uk-pd--20', 'uk-mg--10']
* ```
*/
protected function validateUtilityClasses(array $classes): array {
return array_filter($classes, [$this, 'isValidUtilityClass']);
}
/**
* Checks if a single utility class name is valid.
*
* Uses regular expression to validate that the class name follows
* UtiliKit naming conventions with proper prefix, optional breakpoint,
* property abbreviation, and value.
*
* @param string $class
* The utility class name to validate.
*
* @return bool
* TRUE if the class name follows UtiliKit naming conventions with proper
* prefix, optional breakpoint, property abbreviation, and value format.
* FALSE if the class name is invalid or malformed.
*
* @example
* ```php
* $this->assertTrue($this->isValidUtilityClass('uk-pd--20'));
* $this->assertTrue($this->isValidUtilityClass('uk-md-mg--t-10'));
* $this->assertFalse($this->isValidUtilityClass('invalid-class'));
* ```
*/
protected function isValidUtilityClass(string $class): bool {
// UtiliKit class pattern: uk-[breakpoint-]property--value.
$pattern = '/^uk-(?:(?:sm|md|lg|xl|xxl)-)?[a-z]{2,4}--[a-zA-Z0-9\-_.%]+$/';
return preg_match($pattern, $class) === 1;
}
/**
* Creates test CSS content for validation purposes.
*
* Generates basic CSS rules for the provided utility classes. Useful
* for testing CSS validation methods and ensuring proper rule generation.
*
* @param array $classes
* Array of utility class names to create CSS for.
*
* @return string
* A CSS string containing basic rules for each provided utility class.
* Rules are generated based on common UtiliKit class patterns with
* appropriate property values and units.
*
* @example
* ```php
* $classes = ['uk-pd--20', 'uk-mg--10'];
* $css = $this->createTestCss($classes);
* // Returns: '.uk-pd--20 { padding: 20px; } .uk-mg--10 { margin: 10px; }'
* ```
*/
protected function createTestCss(array $classes): string {
$css = '';
foreach ($classes as $class) {
// Generate basic CSS rule based on class pattern.
if (strpos($class, 'uk-pd--') === 0) {
$value = str_replace('uk-pd--', '', $class);
$css .= ".{$class} { padding: {$value}px; } ";
}
elseif (strpos($class, 'uk-mg--') === 0) {
$value = str_replace('uk-mg--', '', $class);
$css .= ".{$class} { margin: {$value}px; } ";
}
elseif (strpos($class, 'uk-bg--') === 0) {
$value = str_replace('uk-bg--', '', $class);
$css .= ".{$class} { background-color: #{$value}; } ";
}
else {
// Generic rule for other classes.
$css .= ".{$class} { /* Rule for {$class} */ } ";
}
}
return trim($css);
}
/**
* Asserts that two arrays of utility classes are equivalent.
*
* Compares two arrays of utility classes, ignoring order but checking
* for exact matches. Useful for testing class discovery and generation
* algorithms.
*
* @param array $expected
* Expected array of utility classes.
* @param array $actual
* Actual array of utility classes to compare.
* @param string $message
* Optional assertion message.
*
* @example
* ```php
* $expected = ['uk-pd--20', 'uk-mg--10'];
* $actual = ['uk-mg--10', 'uk-pd--20']; // Different order
* $this->assertUtilityClassesEqual($expected, $actual);
* ```
*/
protected function assertUtilityClassesEqual(array $expected, array $actual, string $message = ''): void {
sort($expected);
sort($actual);
$default_message = 'Utility class arrays should be equivalent';
$this->assertEquals($expected, $actual, $message ?: $default_message);
}
}
