['value' => $value]]; } /** Presets in Divi's stored format (formats §3): a module preset nesting a group preset, a group preset. */ private static function overrides(): PresetOverrides { $items = [ 'mmmmmmmmmm' => [ 'type' => 'module', 'id' => 'mmmmmmmmmm', 'name' => 'Blurb Leistung', 'moduleName' => 'divi/blurb', 'attrs' => [ 'module' => ['decoration' => ['spacing' => self::desktop([ 'padding' => ['top' => '20px', 'bottom' => '10px'], ])]], 'title' => ['decoration' => ['font' => ['font' => self::desktop(['weight' => '700'])]]], ], 'groupPresets' => [ 'title.decoration.bodyFont' => ['presetId' => ['ffffffffff'], 'groupName' => 'divi/font-body'], ], ], 'ssssssssss' => [ 'type' => 'group', 'id' => 'ssssssssss', 'name' => 'Abstand Karte', 'groupName' => 'divi/spacing', 'groupId' => self::SPACING, 'moduleName' => 'divi/blurb', 'attrs' => ['module' => ['decoration' => ['spacing' => self::desktop([ 'margin' => ['bottom' => '24px'], ])]]], ], 'ffffffffff' => [ 'type' => 'group', 'id' => 'ffffffffff', 'name' => 'Fließtext', 'groupName' => 'divi/font-body', 'groupId' => self::BODY_FONT, 'moduleName' => 'divi/text', 'attrs' => ['content' => ['decoration' => ['bodyFont' => ['desktop' => [ 'value' => ['size' => '18px'], 'hover' => ['size' => '20px'], ], 'phone' => ['value' => ['size' => '16px']]]]]], ], ]; return new PresetOverrides(static fn (string $id): ?array => $items[$id] ?? null); } public function testLeavesFlattenBreakpointsStatesAndSubpaths(): void { $leaves = PresetOverrides::leaves([ 'builderVersion' => '5.2.1', 'modulePreset' => ['mmmmmmmmmm'], 'groupPreset' => [self::SPACING => ['presetId' => ['ssssssssss'], 'groupName' => 'divi/spacing']], 'module' => ['decoration' => ['spacing' => [ 'desktop' => ['value' => ['padding' => ['top' => '20px'], 'syncVertical' => 'on'], 'hover' => [ 'margin' => ['bottom' => '4px'], ]], 'phone' => ['value' => ['padding' => ['top' => '8px']]], ]]], 'title' => ['innerContent' => self::desktop('Hallo'), 'decoration' => ['font' => ['font' => self::desktop( new stdClass() )]]], 'content' => ['decoration' => ['bodyFont' => self::desktop(['families' => ['Lato', 'Arial']])]], ]); self::assertSame( [ 'content.decoration.bodyFont|desktop|value|families' => ['Lato', 'Arial'], 'module.decoration.spacing|desktop|value|padding.top' => '20px', 'module.decoration.spacing|desktop|value|syncVertical' => 'on', 'module.decoration.spacing|desktop|hover|margin.bottom' => '4px', 'module.decoration.spacing|phone|value|padding.top' => '8px', 'title.innerContent|desktop|value|' => 'Hallo', ], $leaves ); } public function testFindReportsOnlyDifferingValuesOfTheModulePreset(): void { $attrs = [ 'modulePreset' => ['mmmmmmmmmm'], 'module' => ['decoration' => ['spacing' => self::desktop(['padding' => ['top' => '40px', 'bottom' => '10px']])]], // 700 as a number is the same value as "700" (the builder writes strings, an API client may not). 'title' => ['decoration' => ['font' => ['font' => self::desktop(['weight' => 700])]]], ]; self::assertSame( ['mmmmmmmmmm' => ['module.decoration.spacing|desktop|value|padding.top']], self::overrides()->find($attrs) ); } public function testNoOverlapNoReport(): void { // A local padding.left does not cover the preset's padding.top/bottom (array_replace_recursive). $attrs = [ 'modulePreset' => ['mmmmmmmmmm'], 'module' => ['decoration' => ['spacing' => self::desktop(['padding' => ['left' => '5px']])]], 'title' => ['innerContent' => self::desktop('Text')], ]; self::assertSame([], self::overrides()->find($attrs)); self::assertSame([], self::overrides()->find([])); } public function testGroupPresetLeavesMoveFromTheStoredGroupIdToTheBlocksKey(): void { // The preset was made for content.decoration.bodyFont; this block uses it on text.decoration.bodyFont. $attrs = [ 'groupPreset' => ['text.decoration.bodyFont' => ['presetId' => ['ffffffffff'], 'groupName' => 'divi/font-body']], 'text' => ['decoration' => ['bodyFont' => [ 'desktop' => ['value' => ['size' => '22px'], 'hover' => ['size' => '20px']], 'phone' => ['value' => ['size' => '12px']], ]]], // Same path as the preset's stored groupId, but not the block's key: no overlap. 'content' => ['decoration' => ['bodyFont' => self::desktop(['size' => '99px'])]], ]; self::assertSame( ['ffffffffff' => [ 'text.decoration.bodyFont|desktop|value|size', 'text.decoration.bodyFont|phone|value|size', ]], self::overrides()->find($attrs) ); } public function testNestedGroupPresetsOfAnAssignedModulePresetCount(): void { $attrs = [ 'modulePreset' => ['mmmmmmmmmm'], 'title' => ['decoration' => ['bodyFont' => self::desktop(['size' => '30px'])]], ]; // The nested entry sits on title.decoration.bodyFont of the module preset; its leaves move there. self::assertSame( ['ffffffffff' => ['title.decoration.bodyFont|desktop|value|size']], self::overrides()->find($attrs) ); } public function testPresetsNobodyAssignedAreNotCounted(): void { // Default presets ("", "default", "_initial") and unknown IDs are no explicit assignment (ruling R4). $attrs = [ 'modulePreset' => ['', 'default', '_initial', 'unbekannt'], 'module' => ['decoration' => ['spacing' => self::desktop(['padding' => ['top' => '40px']])]], ]; self::assertSame([], self::overrides()->find($attrs)); self::assertSame($attrs, self::overrides()->clear($attrs)); } public function testStackAndSingleStringAreRead(): void { $attrs = [ 'modulePreset' => 'mmmmmmmmmm', 'groupPreset' => [self::SPACING => ['presetId' => 'ssssssssss', 'groupName' => 'divi/spacing']], 'module' => ['decoration' => ['spacing' => self::desktop([ 'padding' => ['bottom' => '11px'], 'margin' => ['bottom' => '1px'], ])]], ]; self::assertSame( [ 'mmmmmmmmmm' => ['module.decoration.spacing|desktop|value|padding.bottom'], 'ssssssssss' => ['module.decoration.spacing|desktop|value|margin.bottom'], ], self::overrides()->find($attrs) ); } public function testClearRemovesLocalLeavesOnPresetLeavesAndEmptyParents(): void { $attrs = [ 'builderVersion' => '5.2.1', 'modulePreset' => ['mmmmmmmmmm'], 'module' => ['decoration' => ['spacing' => self::desktop([ // padding.top is a preset leaf (different value), padding.bottom too (same value), left is not. 'padding' => ['top' => '40px', 'bottom' => '10px', 'left' => '5px'], ])]], 'title' => [ 'innerContent' => self::desktop('Hallo'), 'decoration' => ['font' => ['font' => self::desktop(['weight' => '400'])]], ], ]; self::assertSame( [ 'builderVersion' => '5.2.1', 'modulePreset' => ['mmmmmmmmmm'], 'module' => ['decoration' => ['spacing' => self::desktop(['padding' => ['left' => '5px']])]], 'title' => ['innerContent' => self::desktop('Hallo')], ], self::overrides()->clear($attrs) ); } public function testClearDropsAWholeBranchWhenNothingElseIsLeft(): void { $attrs = [ 'modulePreset' => ['mmmmmmmmmm'], 'title' => ['decoration' => ['font' => ['font' => self::desktop(['weight' => '400'])]]], ]; self::assertSame(['modulePreset' => ['mmmmmmmmmm']], self::overrides()->clear($attrs)); } public function testDescribeFollowsRulingR5(): void { $attrs = [ 'modulePreset' => ['mmmmmmmmmm'], 'groupPreset' => ['text.decoration.bodyFont' => ['presetId' => ['ffffffffff'], 'groupName' => 'divi/font-body']], 'module' => ['decoration' => ['spacing' => self::desktop(['padding' => ['top' => '40px']])]], 'text' => ['decoration' => ['bodyFont' => [ 'desktop' => ['hover' => ['size' => '1px'], 'value' => ['size' => '2px']], 'phone' => ['value' => ['size' => '3px']], ]]], ]; $names = ['mmmmmmmmmm' => 'Blurb Leistung', 'ffffffffff' => 'Fließtext']; self::assertSame( [ 's0.r0.c0.m2: lokale Werte überdecken „Blurb Leistung“: module.decoration.spacing ' . '– clear_overrides: true entfernt sie.', 's0.r0.c0.m2: lokale Werte überdecken „Fließtext“: text.decoration.bodyFont, ' . 'text.decoration.bodyFont (desktop, hover), text.decoration.bodyFont (phone) ' . '– clear_overrides: true entfernt sie.', ], self::overrides()->describe('s0.r0.c0.m2', $attrs, $names) ); } public function testDescribeShowsAtMostFivePathsAndCountsTheRest(): void { $preset = ['id' => 'pppppppppp', 'name' => 'Viele', 'attrs' => []]; $local = ['modulePreset' => ['pppppppppp']]; foreach (range(1, 7) as $number) { $preset['attrs']['module']['decoration']['p' . $number] = self::desktop('a'); $local['module']['decoration']['p' . $number] = self::desktop('b'); } $overrides = new PresetOverrides(static fn (string $id): ?array => $id === 'pppppppppp' ? $preset : null); self::assertSame( ['x1: lokale Werte überdecken „pppppppppp“: module.decoration.p1, module.decoration.p2, ' . 'module.decoration.p3, module.decoration.p4, module.decoration.p5 und 2 weitere ' . '– clear_overrides: true entfernt sie.'], $overrides->describe('x1', $local, []) ); } public function testDescribeIsEmptyWithoutOverrides(): void { self::assertSame([], self::overrides()->describe('s0', ['modulePreset' => ['mmmmmmmmmm']], [])); } }