Files
WP-LIT-MCP/tests/Unit/Integrations/Divi/Design/ThemeOptionSpecTest.php
T

291 lines
16 KiB
PHP

<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
use InvalidArgumentException;
use LIT\MCP\Integrations\Divi\Design\ThemeOptionSpec;
use LIT\MCP\Integrations\Divi\Design\ThemeOptionsAdapter;
use PHPUnit\Framework\TestCase;
final class ThemeOptionSpecTest extends TestCase
{
public function testTheTwentyKeysKeepTheOrderOfRulingW2(): void
{
self::assertSame([
'heading_font', 'body_font', 'primary_nav_font', 'secondary_nav_font',
'heading_font_weight', 'body_font_weight',
'body_font_size', 'tablet_body_font_size', 'phone_body_font_size',
'body_header_size', 'tablet_header_font_size', 'phone_header_font_size',
'body_font_height', 'body_header_height', 'body_header_spacing', 'body_header_style',
'content_width', 'gutter_width', 'section_padding', 'row_padding',
], ThemeOptionSpec::KEYS);
}
public function testTheKeysAreTheKeysOfTheAdapterSanitizersInTheSameOrder(): void
{
self::assertSame(array_keys(ThemeOptionsAdapter::SANITIZERS), ThemeOptionSpec::KEYS);
}
/** @return array<string, array{string, string, int|float|string|null, int|float|null, int|float|null}> */
public static function definitions(): array
{
return [
'heading_font' => ['heading_font', 'font', null, null, null],
'body_font' => ['body_font', 'font', null, null, null],
'primary_nav_font' => ['primary_nav_font', 'font', null, null, null],
'secondary_nav_font' => ['secondary_nav_font', 'font', null, null, null],
'heading_font_weight' => ['heading_font_weight', 'weight', '500', null, null],
'body_font_weight' => ['body_font_weight', 'weight', '500', null, null],
'body_font_size' => ['body_font_size', 'int', 14, 10, 32],
'tablet_body_font_size' => ['tablet_body_font_size', 'int', 14, 10, 32],
'phone_body_font_size' => ['phone_body_font_size', 'int', 14, 10, 32],
'body_header_size' => ['body_header_size', 'int', 30, 22, 72],
'tablet_header_font_size' => ['tablet_header_font_size', 'int', 30, 22, 72],
'phone_header_font_size' => ['phone_header_font_size', 'int', 30, 22, 72],
'body_font_height' => ['body_font_height', 'float', 1.7, 0.8, 3],
'body_header_height' => ['body_header_height', 'float', 1.0, 0.8, 3],
'body_header_spacing' => ['body_header_spacing', 'int', 0, -2, 10],
'body_header_style' => ['body_header_style', 'style', null, null, null],
'content_width' => ['content_width', 'int', 1080, 960, 1920],
'gutter_width' => ['gutter_width', 'int', 3, 1, 4],
'section_padding' => ['section_padding', 'int', 4, 0, 10],
'row_padding' => ['row_padding', 'int', 2, 0, 10],
];
}
/** @dataProvider definitions */
public function testEveryKeyHasItsDefinition(
string $key,
string $type,
mixed $default,
mixed $min,
mixed $max
): void
{
$definition = ThemeOptionSpec::definition($key);
self::assertTrue(ThemeOptionSpec::isKey($key));
self::assertSame($type, $definition['type']);
self::assertSame($default, $definition['default']);
self::assertNotSame('', $definition['label']);
self::assertSame($min, $definition['min'] ?? null);
self::assertSame($max, $definition['max'] ?? null);
self::assertSame(in_array($type, ['int', 'float'], true), isset($definition['step']));
self::assertSame(in_array($type, ['weight', 'style'], true), isset($definition['choices']));
}
public function testInheritanceAndChoicesAreDefined(): void
{
self::assertSame('body_font_size', ThemeOptionSpec::definition('tablet_body_font_size')['inherits']);
self::assertSame('tablet_body_font_size', ThemeOptionSpec::definition('phone_body_font_size')['inherits']);
self::assertSame('body_header_size', ThemeOptionSpec::definition('tablet_header_font_size')['inherits']);
self::assertSame('tablet_header_font_size', ThemeOptionSpec::definition('phone_header_font_size')['inherits']);
self::assertArrayNotHasKey('inherits', ThemeOptionSpec::definition('body_font_size'));
self::assertSame(
['100', '200', '300', '400', '500', '600', '700', '800', '900'],
ThemeOptionSpec::definition('body_font_weight')['choices']
);
self::assertSame(
['italic', 'uppercase', 'underline'],
ThemeOptionSpec::definition('body_header_style')['choices']
);
self::assertSame(0.1, ThemeOptionSpec::definition('body_font_height')['step']);
}
public function testAnUnknownKeyIsNoKeyAndHasNoDefinition(): void
{
self::assertFalse(ThemeOptionSpec::isKey('accent_color'));
$this->expectException(InvalidArgumentException::class);
ThemeOptionSpec::definition('accent_color');
}
/** @return array<string, array{string, mixed}> */
public static function validValues(): array
{
return [
'int lower bound' => ['body_font_size', 10],
'int upper bound' => ['body_font_size', 32],
'int as digits' => ['body_font_size', '15'],
'int as whole float' => ['body_font_size', 15.0],
'negative int bound' => ['body_header_spacing', -2],
'negative digits' => ['body_header_spacing', '-1'],
'width bounds' => ['content_width', 1920],
'zero padding' => ['section_padding', 0],
'float lower bound' => ['body_font_height', 0.8],
'float upper bound as int' => ['body_font_height', 3],
'float on a step' => ['body_font_height', 1.7],
'float as digits' => ['body_header_height', '1.2'],
'float from int' => ['body_header_height', 1],
'font' => ['heading_font', 'Open Sans'],
'font with umlaut and padding' => ['body_font', " Fjalla Önö \t"],
'font of 100 characters' => ['primary_nav_font', str_repeat('a', 100)],
'weight as text' => ['heading_font_weight', '700'],
'weight as int' => ['body_font_weight', 400],
'style' => ['body_header_style', 'uppercase'],
];
}
/** @dataProvider validValues */
public function testCheckAcceptsValidValues(string $key, mixed $value): void
{
self::assertNull(ThemeOptionSpec::check($key, $value));
}
/** @return array<string, array{string, mixed, string}> */
public static function invalidValues(): array
{
return [
'int below' => ['body_font_size', 9, 'außerhalb von 10 bis 32'],
'int above' => ['body_font_size', 33, 'außerhalb von 10 bis 32'],
'int digits above' => ['content_width', '1921', 'außerhalb von 960 bis 1920'],
'negative below' => ['body_header_spacing', -3, 'außerhalb von -2 bis 10'],
'float with a fraction for an int key' => ['body_font_size', 15.5, 'ganze Zahl'],
'digits with a fraction for an int key' => ['row_padding', '2.5', 'ganze Zahl'],
'non-numeric text' => ['body_font_size', 'groß', 'erwartet eine Zahl, erhalten "groß"'],
'unit suffix' => ['body_font_size', '15px', 'erwartet eine Zahl'],
'exponent' => ['body_font_size', '1e1', 'erwartet eine Zahl'],
'spaces around digits' => ['body_font_size', ' 15', 'erwartet eine Zahl'],
'empty text' => ['gutter_width', '', 'erwartet eine Zahl'],
'true' => ['body_font_size', true, 'erhalten true'],
'false' => ['body_font_size', false, 'erhalten false'],
'null' => ['body_font_size', null, 'erhalten null'],
'array' => ['body_font_size', [14], 'erhalten einen array'],
'infinity' => ['body_font_height', INF, 'erwartet eine Zahl'],
'not a number' => ['body_font_height', NAN, 'erwartet eine Zahl'],
'float below' => ['body_font_height', 0.7, 'außerhalb von 0,8 bis 3'],
'float above' => ['body_header_height', 3.1, 'außerhalb von 0,8 bis 3'],
'float off the step' => ['body_font_height', 1.75, 'Schrittweite 0,1 (ab 0,8)'],
'float digits off the step' => ['body_header_height', '1.05', 'Schrittweite'],
'font as number' => ['heading_font', 12, 'erwartet einen Schriftnamen'],
'font as false' => ['heading_font', false, 'erwartet einen Schriftnamen'],
'font as null' => ['heading_font', null, 'erhalten null'],
'empty font' => ['body_font', '', '1 bis 100 Zeichen'],
'blank font' => ['body_font', " \t ", '1 bis 100 Zeichen'],
'font of 101 characters' => ['body_font', str_repeat('a', 101), '1 bis 100 Zeichen'],
'font with a control character' => ['body_font', "Open\nSans", 'unzulässige Zeichen'],
'font with angle brackets' => ['body_font', 'Open<Sans>', 'unzulässige Zeichen'],
'font with a quote' => ['primary_nav_font', 'Open "Sans"', 'unzulässige Zeichen'],
'font with an apostrophe' => ['secondary_nav_font', "Open'Sans", 'unzulässige Zeichen'],
'font with invalid utf-8' => ['body_font', "Open\xC3(Sans", 'unzulässige Zeichen'],
'font none' => ['heading_font', 'None', 'null setzt auf Divis Standardschrift zurück'],
'weight off the list' => ['body_font_weight', '550', 'erlaubt ist genau ein Wert aus 100, 200'],
'weight as float' => ['body_font_weight', 500.0, 'erhalten 500'],
'weight word' => ['heading_font_weight', 'bold', 'erhalten "bold"'],
'weight as null' => ['heading_font_weight', null, 'erhalten null'],
'style combination' => ['body_header_style', 'italic|uppercase', 'genau ein Wert'],
'style empty' => ['body_header_style', '', 'genau ein Wert'],
'style bold' => ['body_header_style', 'bold', 'genau ein Wert'],
'style case' => ['body_header_style', 'Italic', 'genau ein Wert'],
'style as list' => ['body_header_style', ['italic'], 'erhalten einen array'],
];
}
/** @dataProvider invalidValues */
public function testCheckRejectsInvalidValuesWithAGermanMessage(string $key, mixed $value, string $needle): void
{
$message = ThemeOptionSpec::check($key, $value);
self::assertIsString($message);
self::assertStringContainsString($needle, $message);
self::assertStringStartsWith(ThemeOptionSpec::definition($key)['label'] . ':', $message);
}
/** @return array<string, array{string}> */
public static function everyKey(): array
{
return array_map(static fn (string $key): array => [$key], array_combine(
ThemeOptionSpec::KEYS,
ThemeOptionSpec::KEYS
));
}
/**
* The adapter's sanitizers let booleans through is_scalar (absint(true) = 1), so check() must stop them.
*
* @dataProvider everyKey
*/
public function testCheckRejectsBooleansNullArraysAndObjectsForEveryKey(string $key): void
{
foreach ([true, false, null, [], [1], new \stdClass()] as $value) {
self::assertIsString(ThemeOptionSpec::check($key, $value), $key . ' accepted ' . get_debug_type($value));
}
}
public function testCheckOfAnUnknownKeyThrows(): void
{
$this->expectException(InvalidArgumentException::class);
ThemeOptionSpec::check('logo', 'x');
}
public function testNormalizeCastsToTheTypeOfTheKey(): void
{
self::assertSame(15, ThemeOptionSpec::normalize('body_font_size', '15'));
self::assertSame(15, ThemeOptionSpec::normalize('body_font_size', 15.0));
self::assertSame(-1, ThemeOptionSpec::normalize('body_header_spacing', '-1'));
self::assertSame(1.7, ThemeOptionSpec::normalize('body_font_height', '1.7'));
self::assertSame(1.0, ThemeOptionSpec::normalize('body_header_height', 1));
self::assertSame('700', ThemeOptionSpec::normalize('heading_font_weight', 700));
self::assertSame('400', ThemeOptionSpec::normalize('body_font_weight', '400'));
self::assertSame('Open Sans', ThemeOptionSpec::normalize('heading_font', " Open Sans\t"));
self::assertSame('italic', ThemeOptionSpec::normalize('body_header_style', 'italic'));
}
public function testEffectiveFallsBackToTheDivisDefaultsWhenNothingIsStored(): void
{
self::assertSame(14, ThemeOptionSpec::effective('body_font_size', []));
self::assertSame(1.7, ThemeOptionSpec::effective('body_font_height', []));
self::assertSame('500', ThemeOptionSpec::effective('heading_font_weight', []));
self::assertSame(1080, ThemeOptionSpec::effective('content_width', []));
self::assertNull(ThemeOptionSpec::effective('heading_font', []));
self::assertNull(ThemeOptionSpec::effective('body_header_style', []));
self::assertSame(14, ThemeOptionSpec::effective('phone_body_font_size', []));
self::assertSame(30, ThemeOptionSpec::effective('phone_header_font_size', []));
}
public function testEffectiveInheritsTheNextLargerSizeWhileUnset(): void
{
$stored = ['body_font_size' => 18, 'body_header_size' => 40];
self::assertSame(18, ThemeOptionSpec::effective('tablet_body_font_size', $stored));
self::assertSame(18, ThemeOptionSpec::effective('phone_body_font_size', $stored));
self::assertSame(40, ThemeOptionSpec::effective('tablet_header_font_size', $stored));
self::assertSame(40, ThemeOptionSpec::effective('phone_header_font_size', $stored));
}
public function testAStoredOverrideBreaksTheChainAtItsLevelOnly(): void
{
$stored = ['body_font_size' => 18, 'tablet_body_font_size' => 16, 'tablet_header_font_size' => 28];
self::assertSame(18, ThemeOptionSpec::effective('body_font_size', $stored));
self::assertSame(16, ThemeOptionSpec::effective('tablet_body_font_size', $stored));
self::assertSame(16, ThemeOptionSpec::effective('phone_body_font_size', $stored));
self::assertSame(28, ThemeOptionSpec::effective('phone_header_font_size', $stored));
self::assertSame(30, ThemeOptionSpec::effective('body_header_size', $stored));
$stored['phone_body_font_size'] = '12';
self::assertSame(12, ThemeOptionSpec::effective('phone_body_font_size', $stored));
}
public function testEffectiveTreatsFalseAndGarbageAsUnsetAndCastsStoredDigits(): void
{
self::assertNull(ThemeOptionSpec::effective('heading_font', ['heading_font' => false]));
self::assertNull(ThemeOptionSpec::effective('heading_font', ['heading_font' => 'none']));
self::assertSame('Lato', ThemeOptionSpec::effective('heading_font', ['heading_font' => 'Lato']));
self::assertSame(14, ThemeOptionSpec::effective('body_font_size', ['body_font_size' => 'groß']));
self::assertSame(15, ThemeOptionSpec::effective('body_font_size', ['body_font_size' => '15']));
self::assertSame(1.2, ThemeOptionSpec::effective('body_font_height', ['body_font_height' => '1.2']));
self::assertSame('300', ThemeOptionSpec::effective('body_font_weight', ['body_font_weight' => 300]));
self::assertSame(40, ThemeOptionSpec::effective('body_font_size', ['body_font_size' => 40]), 'no range check');
self::assertSame(18, ThemeOptionSpec::effective('tablet_body_font_size', [
'body_font_size' => 18, 'tablet_body_font_size' => false,
]));
}
public function testEffectiveOfAnUnknownKeyThrows(): void
{
$this->expectException(InvalidArgumentException::class);
ThemeOptionSpec::effective('nope', []);
}
}