utilikit-1.0.0/tests/src/Unit/UtilikitContentScannerTest.php
tests/src/Unit/UtilikitContentScannerTest.php
<?php
declare(strict_types=1);
namespace Drupal\Tests\utilikit\Unit;
use Drupal\Tests\UnitTestCase;
use Drupal\utilikit\Service\UtilikitContentScanner;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
/**
* Tests the UtilikitContentScanner service.
*
* @group utilikit
* @coversDefaultClass \Drupal\utilikit\Service\UtilikitContentScanner
*/
class UtilikitContentScannerTest extends UnitTestCase {
/**
* The content scanner service under test.
*
* @var \Drupal\utilikit\Service\UtilikitContentScanner
*/
protected UtilikitContentScanner $contentScanner;
/**
* Mock entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface&\PHPUnit\Framework\MockObject\MockObject
*/
protected EntityTypeManagerInterface&MockObject $entityTypeManager;
/**
* Mock logger.
*
* @var \Psr\Log\LoggerInterface&\PHPUnit\Framework\MockObject\MockObject
*/
protected LoggerInterface&MockObject $logger;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->contentScanner = new UtilikitContentScanner(
$this->entityTypeManager,
$this->logger
);
}
/**
* Tests extractClassesFromHtml with valid HTML.
*
* @covers ::extractClassesFromHtml
* @dataProvider providerValidHtml
*/
public function testExtractClassesFromHtmlValid(string $html, array $expected): void {
$result = $this->contentScanner->extractClassesFromHtml($html);
$this->assertEquals($expected, $result);
}
/**
* Data provider for valid HTML extraction tests.
*
* @return array<string, array>
* html: string, expected: array<string>
*/
public static function providerValidHtml(): array {
return [
'single utility class' => [
'html' => '<div class="utilikit uk-pd--20">Content</div>',
'expected' => ['uk-pd--20'],
],
'multiple utility classes' => [
'html' => '<div class="utilikit uk-pd--20 uk-mg--10 uk-bg--ff0000">Content</div>',
'expected' => ['uk-pd--20', 'uk-mg--10', 'uk-bg--ff0000'],
],
'nested elements' => [
'html' => '<div class="utilikit uk-pd--20"><span class="utilikit uk-tc--333333">Text</span></div>',
'expected' => ['uk-pd--20', 'uk-tc--333333'],
],
'responsive classes' => [
'html' => '<div class="utilikit uk-pd--20 uk-md-pd--30 uk-lg-pd--40">Content</div>',
'expected' => ['uk-pd--20', 'uk-md-pd--30', 'uk-lg-pd--40'],
],
'mixed with non-utility classes' => [
'html' => '<div class="utilikit uk-pd--20 other-class uk-mg--10 random">Content</div>',
'expected' => ['uk-pd--20', 'uk-mg--10'],
],
'no utilikit marker class' => [
'html' => '<div class="uk-pd--20 uk-mg--10">Content</div>',
'expected' => [],
],
'empty class attribute' => [
'html' => '<div class="">Content</div>',
'expected' => [],
],
'no classes' => [
'html' => '<div>Content</div>',
'expected' => [],
],
];
}
/**
* Tests scanEntity method with fieldable entity.
*
* @covers ::scanEntity
*/
public function testScanEntityFieldable(): void {
$entity = $this->createMock(FieldableEntityInterface::class);
// Create mock field with utility classes.
$field = $this->createMock(FieldItemListInterface::class);
$fieldItem = $this->createMock(FieldItemInterface::class);
$property = $this->createMock(TypedDataInterface::class);
$htmlContent = '<div class="utilikit uk-pd--20 uk-mg--10">Test content</div>';
$property->method('getValue')->willReturn($htmlContent);
$fieldItem->method('getProperties')->willReturn([$property]);
$field->method('getIterator')->willReturn(new \ArrayIterator([$fieldItem]));
$entity->method('getFields')->willReturn([$field]);
$result = $this->contentScanner->scanEntity($entity);
$this->assertCount(2, $result);
$this->assertContains('uk-pd--20', $result);
$this->assertContains('uk-mg--10', $result);
}
/**
* Tests scanEntity method with non-fieldable entity.
*
* @covers ::scanEntity
*/
public function testScanEntityNonFieldable(): void {
$entity = $this->createMock('\Drupal\Core\Entity\EntityInterface');
$result = $this->contentScanner->scanEntity($entity);
$this->assertEquals([], $result);
}
/**
* Tests scanEntityBatch method.
*
* @covers ::scanEntityBatch
*/
public function testScanEntityBatch(): void {
$entities = [];
// Create mock entities with different classes.
for ($i = 0; $i < 3; $i++) {
$entity = $this->createMock(FieldableEntityInterface::class);
$field = $this->createMock(FieldItemListInterface::class);
$fieldItem = $this->createMock(FieldItemInterface::class);
$property = $this->createMock(TypedDataInterface::class);
$classes = ['uk-pd--' . (20 + $i * 10), 'uk-mg--' . (10 + $i * 5)];
$htmlContent = sprintf(
'<div class="utilikit %s">Content %d</div>',
implode(' ', $classes),
$i
);
$property->method('getValue')->willReturn($htmlContent);
$fieldItem->method('getProperties')->willReturn([$property]);
$field->method('getIterator')->willReturn(new \ArrayIterator([$fieldItem]));
$entity->method('getFields')->willReturn([$field]);
$entities[] = $entity;
}
$result = $this->contentScanner->scanEntityBatch($entities);
$expected = [
'uk-pd--20', 'uk-mg--10',
'uk-pd--30', 'uk-mg--15',
'uk-pd--40', 'uk-mg--20',
];
$this->assertEquals(sorted($expected), sorted($result));
}
/**
* Tests edge cases in HTML extraction.
*
* @covers ::extractClassesFromHtml
*/
public function testExtractClassesEdgeCases(): void {
// Test malformed HTML.
$html = '<div class="utilikit uk-pd--20" <span>Broken</div>';
$result = $this->contentScanner->extractClassesFromHtml($html);
$this->assertEquals(['uk-pd--20'], $result);
// Test with special characters in classes.
$html = '<div class="utilikit uk-pd--20 uk-tc--ff0000-50">Content</div>';
$result = $this->contentScanner->extractClassesFromHtml($html);
$this->assertEquals(['uk-pd--20', 'uk-tc--ff0000-50'], $result);
// Test with extra whitespace.
$html = '<div class=" utilikit uk-pd--20 uk-mg--10 ">Content</div>';
$result = $this->contentScanner->extractClassesFromHtml($html);
$this->assertEquals(['uk-pd--20', 'uk-mg--10'], $result);
}
/**
* Tests performance with large content containing many utility classes.
*
* @covers ::extractClassesFromHtml
*/
public function testPerformanceWithLargeContent(): void {
// Generate large content with many utility classes.
$classCount = 500;
$uniqueClasses = [];
$htmlContent = '<div class="utilikit">';
// Generate diverse utility classes.
for ($i = 0; $i < $classCount; $i++) {
$class = sprintf('uk-%s--%d', ['pd', 'mg', 'bg', 'tc', 'dp'][$i % 5], $i);
$uniqueClasses[] = $class;
$htmlContent .= sprintf('<span class="utilikit %s">Item %d</span>', $class, $i);
}
$htmlContent .= '</div>';
$startTime = microtime(TRUE);
$result = $this->contentScanner->extractClassesFromHtml($htmlContent);
$endTime = microtime(TRUE);
// Verify all classes were extracted.
$this->assertCount($classCount, $result);
foreach ($uniqueClasses as $class) {
$this->assertContains($class, $result);
}
// Performance assertion - should process 500 classes in under 0.1 seconds.
$this->assertLessThan(0.1, $endTime - $startTime, 'Large content scanning took too long');
}
/**
* Tests scanAllContent method.
*
* @covers ::scanAllContent
*/
public function testScanAllContent(): void {
// Set up entity types.
$entityTypes = ['node', 'block_content'];
foreach ($entityTypes as $entityType) {
$storage = $this->createMock(EntityStorageInterface::class);
$entityTypeDefinition = $this->createMock(EntityTypeInterface::class);
// Mock entities for each type.
$entities = [];
for ($i = 0; $i < 2; $i++) {
$entity = $this->createMock(FieldableEntityInterface::class);
$field = $this->createMock(FieldItemListInterface::class);
$fieldItem = $this->createMock(FieldItemInterface::class);
$property = $this->createMock(TypedDataInterface::class);
$class = sprintf('uk-%s--%d', $entityType, $i);
$htmlContent = sprintf('<div class="utilikit %s">Content</div>', $class);
$property->method('getValue')->willReturn($htmlContent);
$fieldItem->method('getProperties')->willReturn([$property]);
$field->method('getIterator')->willReturn(new \ArrayIterator([$fieldItem]));
$entity->method('getFields')->willReturn([$field]);
$entities[] = $entity;
}
$storage->method('loadMultiple')->willReturn($entities);
$this->entityTypeManager
->method('getStorage')
->with($entityType)
->willReturn($storage);
$this->entityTypeManager
->method('getDefinition')
->with($entityType)
->willReturn($entityTypeDefinition);
}
$this->entityTypeManager
->method('getDefinitions')
->willReturn(['node' => TRUE, 'block_content' => TRUE]);
$result = $this->contentScanner->scanAllContent();
$this->assertIsArray($result);
$this->assertArrayHasKey('classes', $result);
$this->assertArrayHasKey('scanned_count', $result);
// 2 entities * 2 types
$this->assertCount(4, $result['classes']);
$this->assertEquals(4, $result['scanned_count']);
}
/**
* Tests duplicate class removal.
*
* @covers ::extractClassesFromHtml
*/
public function testDuplicateClassRemoval(): void {
$html = '<div class="utilikit uk-pd--20 uk-pd--20 uk-mg--10 uk-mg--10">Content</div>';
$result = $this->contentScanner->extractClassesFromHtml($html);
// Should have no duplicates.
$this->assertCount(2, $result);
$this->assertEquals(['uk-pd--20', 'uk-mg--10'], $result);
}
/**
* Helper function to sort arrays for comparison.
*
* @param array<string> $array
* Array to sort.
*
* @return array<string>
* Sorted array.
*/
protected function sorted(array $array): array {
sort($array);
return $array;
}
}
