[ 'type' => 'module', 'id' => $id, 'name' => $name, 'moduleName' => 'divi/blurb', 'attrs' => $attrs, ]; $group = static fn (string $id, string $name, string $groupName, string $groupId, array $attrs = []): array => [ 'type' => 'group', 'id' => $id, 'name' => $name, 'groupName' => $groupName, 'groupId' => $groupId, 'moduleName' => 'divi/blurb', 'attrs' => $attrs, ]; return [ 'module' => ['divi/blurb' => ['default' => '', 'items' => [ 'bl00000001' => $module('bl00000001', 'Leistung', [ 'title' => ['decoration' => ['font' => ['font' => ['desktop' => ['value' => ['weight' => '700']]]]]], ]), 'bl00000002' => $module('bl00000002', 'Karte'), 'bl00000003' => $module('bl00000003', 'Doppelt'), 'bl00000004' => $module('bl00000004', 'doppelt'), 'ba!d000005' => $module('ba!d000005', 'Kaputt'), ]]], 'group' => [ 'divi/font-body' => ['default' => '', 'items' => [ 'fb00000001' => $group('fb00000001', 'Fließtext', 'divi/font-body', 'content.decoration.bodyFont'), 'fb00000002' => $group('fb00000002', 'Fließtext groß', 'divi/font-body', 'content.decoration.bodyFont'), 'fb00000003' => $group('fb00000003', 'Doppelt', 'divi/font-body', 'content.decoration.bodyFont'), 'fb00000004' => $group('fb00000004', 'Doppelt', 'divi/font-body', 'content.decoration.bodyFont'), ]], 'divi/spacing' => ['default' => '', 'items' => [ 'sp00000001' => $group('sp00000001', 'Abstand Karte', 'divi/spacing', 'module.decoration.spacing'), ]], 'divi/font' => ['default' => '', 'items' => [ 'ft00000001' => $group('ft00000001', 'Titelschrift', 'divi/font', 'title.decoration.font'), ]], ], ]; } private function assignment(?Closure $book = null): PresetAssignment { return new PresetAssignment( $book ?? function (): PresetBook { ++$this->bookCalls; return PresetBook::fromDivi(self::stored()); }, static fn (string $module): array => $module === 'divi/blurb' ? [ 'title.decoration.font' => 'divi/font', 'content.decoration.bodyFont' => 'divi/font-body', 'module.decoration.spacing' => 'divi/spacing', 'button.decoration.spacing' => 'divi/spacing', ] : [], static fn (string $id): bool => preg_match('/^[0-9a-z]+$/D', $id) === 1 ); } public function testSingleNameOrIdBecomesAStackOfOne(): void { $byName = $this->assignment()->apply('divi/blurb', ['x' => 1], ['preset' => 'LEISTUNG'], self::PATH); $byId = $this->assignment()->apply('divi/blurb', [], ['preset' => 'bl00000002'], self::PATH); self::assertSame([], $byName['errors']); self::assertSame(['x' => 1, 'modulePreset' => ['bl00000001']], $byName['attrs']); self::assertSame(['modulePreset' => ['bl00000002']], $byId['attrs']); } public function testListKeepsTheOrderOfTheStack(): void { $result = $this->assignment()->apply( 'divi/blurb', ['modulePreset' => ['alt']], ['preset' => ['Karte', 'bl00000001']], self::PATH ); self::assertSame([], $result['errors']); self::assertSame(['modulePreset' => ['bl00000002', 'bl00000001']], $result['attrs']); } public function testNullAndEmptyStringRemoveThePresetWithoutLoadingTheBook(): void { foreach ([null, ''] as $preset) { $result = $this->assignment()->apply( 'divi/blurb', ['modulePreset' => ['bl00000001'], 'builderVersion' => '5.2.1'], ['preset' => $preset], self::PATH ); self::assertSame([], $result['errors']); self::assertSame(['builderVersion' => '5.2.1'], $result['attrs']); } self::assertSame(0, $this->bookCalls); } public function testSpecWithoutPresetKeysChangesNothing(): void { $attrs = ['modulePreset' => ['bl00000001']]; $result = $this->assignment()->apply('divi/blurb', $attrs, ['label' => 'egal'], self::PATH); self::assertSame(['attrs' => $attrs, 'errors' => []], $result); self::assertSame(0, $this->bookCalls); } public function testStackErrorsLeaveTheStackUntouched(): void { $attrs = ['modulePreset' => ['alt']]; $assignment = $this->assignment(); $unknown = $assignment->apply('divi/blurb', $attrs, ['preset' => 'Gibt es nicht'], self::PATH); self::assertSame( ['items[0].preset' => 'unbekanntes Preset; vorhanden für divi/blurb: Leistung, Karte, Doppelt, doppelt, Kaputt'], $unknown['errors'] ); self::assertSame($attrs, $unknown['attrs']); $ambiguous = $assignment->apply('divi/blurb', $attrs, ['preset' => ['Karte', 'Doppelt']], self::PATH); self::assertSame( ['items[0].preset.1' => 'Preset-Name "Doppelt" ist mehrdeutig; bitte die ID angeben: ' . 'bl00000003, bl00000004.'], $ambiguous['errors'] ); self::assertSame($attrs, $ambiguous['attrs']); $duplicate = $assignment->apply('divi/blurb', $attrs, ['preset' => ['Karte', 'bl00000001', 'bl00000002']], self::PATH); self::assertSame(['items[0].preset.2' => 'Preset "bl00000002" steht mehrfach im Stapel'], $duplicate['errors']); $invalid = $assignment->apply('divi/blurb', $attrs, ['preset' => 'Kaputt'], self::PATH); self::assertSame(['items[0].preset' => 'Preset-ID "ba!d000005" ist ungültig'], $invalid['errors']); self::assertSame($attrs, $invalid['attrs']); } public function testStackShapeErrors(): void { $assignment = $this->assignment(); $empty = $assignment->apply('divi/blurb', [], ['preset' => []], self::PATH); self::assertSame(['items[0].preset' => 'leere Liste – null entfernt das Preset'], $empty['errors']); $tooMany = $assignment->apply('divi/blurb', [], ['preset' => array_fill(0, 11, 'Karte')], self::PATH); self::assertSame(['items[0].preset' => 'höchstens 10 Presets im Stapel'], $tooMany['errors']); $notText = $assignment->apply('divi/blurb', [], ['preset' => ['Karte', 5]], self::PATH); self::assertSame(['items[0].preset.1' => 'Preset-Name oder -ID als Text erwartet'], $notText['errors']); $object = $assignment->apply('divi/blurb', [], ['preset' => ['a' => 'Karte']], self::PATH); self::assertSame(['items[0].preset' => 'Text, Liste von Texten oder null erwartet'], $object['errors']); $number = $assignment->apply('divi/blurb', [], ['preset' => 7], self::PATH); self::assertSame(['items[0].preset' => 'Text, Liste von Texten oder null erwartet'], $number['errors']); } public function testGroupPresetsByShortNameFullNameAndPath(): void { $result = $this->assignment()->apply('divi/blurb', ['builderVersion' => '5.2.1'], ['group_presets' => [ 'font-body' => 'Fließtext', 'divi/font' => 'ft00000001', 'button.decoration.spacing' => ['Abstand Karte'], ]], self::PATH); self::assertSame([], $result['errors']); self::assertSame( [ 'builderVersion' => '5.2.1', 'groupPreset' => [ 'content.decoration.bodyFont' => ['presetId' => ['fb00000001'], 'groupName' => 'divi/font-body'], 'title.decoration.font' => ['presetId' => ['ft00000001'], 'groupName' => 'divi/font'], 'button.decoration.spacing' => ['presetId' => ['sp00000001'], 'groupName' => 'divi/spacing'], ], ], $result['attrs'] ); } public function testGroupPresetListIsAStackAndOtherGroupsStay(): void { $attrs = ['groupPreset' => [ 'title.decoration.font' => ['presetId' => ['ft00000001'], 'groupName' => 'divi/font'], ]]; $result = $this->assignment()->apply( 'divi/blurb', $attrs, ['group_presets' => ['font-body' => ['Fließtext', 'Fließtext groß']]], self::PATH ); self::assertSame([], $result['errors']); self::assertSame(['fb00000001', 'fb00000002'], $result['attrs']['groupPreset']['content.decoration.bodyFont']['presetId']); self::assertSame($attrs['groupPreset']['title.decoration.font'], $result['attrs']['groupPreset']['title.decoration.font']); } public function testNullRemovesTheGroupEntryAndAnEmptyGroupPresetDisappears(): void { $attrs = ['groupPreset' => [ 'title.decoration.font' => ['presetId' => ['ft00000001'], 'groupName' => 'divi/font'], 'content.decoration.bodyFont' => ['presetId' => ['fb00000001'], 'groupName' => 'divi/font-body'], ]]; $one = $this->assignment()->apply('divi/blurb', $attrs, ['group_presets' => ['font' => null]], self::PATH); self::assertSame(['content.decoration.bodyFont'], array_keys($one['attrs']['groupPreset'])); $both = $this->assignment()->apply( 'divi/blurb', $attrs, ['group_presets' => ['font' => null, 'font-body' => null]], self::PATH ); self::assertSame([], $both['errors']); self::assertSame([], $both['attrs']); } public function testGroupKeyErrors(): void { $assignment = $this->assignment(); $unknown = $assignment->apply('divi/blurb', [], ['group_presets' => ['sizing' => 'X']], self::PATH); self::assertSame( ['items[0].group_presets.sizing' => 'keine Gruppe "sizing" in divi/blurb; vorhanden: font, font-body, spacing'], $unknown['errors'] ); $ambiguous = $assignment->apply('divi/blurb', [], ['group_presets' => ['spacing' => 'Abstand Karte']], self::PATH); self::assertSame( ['items[0].group_presets.spacing' => 'Gruppe "spacing" ist mehrdeutig; bitte den Attributpfad angeben: ' . 'module.decoration.spacing, button.decoration.spacing.'], $ambiguous['errors'] ); $missing = $assignment->apply('divi/divider', [], ['group_presets' => ['font-body' => 'Fließtext']], self::PATH); self::assertSame( ['items[0].group_presets.font-body' => 'keine Gruppe "font-body" in divi/divider; vorhanden: (keine)'], $missing['errors'] ); } public function testGroupPresetValueErrors(): void { $assignment = $this->assignment(); $result = $assignment->apply('divi/blurb', [], ['group_presets' => [ 'font-body' => 'Gibt es nicht', 'font' => 7, 'module.decoration.spacing' => [], ]], self::PATH); self::assertSame( [ 'items[0].group_presets.font-body' => 'unbekanntes Gruppen-Preset "Gibt es nicht" (divi/font-body)', 'items[0].group_presets.font' => 'Text, Liste von Texten oder null erwartet', 'items[0].group_presets.module.decoration.spacing' => 'leere Liste – null entfernt das Gruppen-Preset', ], $result['errors'] ); self::assertSame([], $result['attrs']); $ambiguous = $assignment->apply('divi/blurb', [], ['group_presets' => ['font-body' => 'Doppelt']], self::PATH); self::assertSame( ['items[0].group_presets.font-body' => 'Preset-Name "Doppelt" ist mehrdeutig; bitte die ID angeben: ' . 'fb00000003, fb00000004.'], $ambiguous['errors'] ); $duplicate = $assignment->apply( 'divi/blurb', [], ['group_presets' => ['font-body' => ['Fließtext', 'fb00000001']]], self::PATH ); self::assertSame( ['items[0].group_presets.font-body' => 'Preset "fb00000001" steht mehrfach im Stapel'], $duplicate['errors'] ); $shape = $assignment->apply('divi/blurb', [], ['group_presets' => 'font-body'], self::PATH); self::assertSame(['items[0].group_presets' => 'Objekt Gruppe → Preset erwartet'], $shape['errors']); } public function testModulePresetFromAnotherModuleIsUnknown(): void { $result = $this->assignment()->apply('divi/text', [], ['preset' => 'Leistung'], self::PATH); self::assertSame(['items[0].preset' => 'unbekanntes Preset; vorhanden für divi/text: (keine)'], $result['errors']); } public function testBookIsLoadedLazilyAndAtMostOnce(): void { $assignment = $this->assignment(); self::assertSame(0, $this->bookCalls); $assignment->apply('divi/blurb', [], ['preset' => 'Karte', 'group_presets' => ['font' => 'Titelschrift']], self::PATH); $assignment->names(); $assignment->clearOverrides(['modulePreset' => ['bl00000001']]); $assignment->overrides()->find(['modulePreset' => ['bl00000001']]); self::assertSame(1, $this->bookCalls); } public function testNamesListsEveryPreset(): void { $names = $this->assignment()->names(); self::assertSame('Leistung', $names['bl00000001']); self::assertSame('Fließtext groß', $names['fb00000002']); self::assertSame('Titelschrift', $names['ft00000001']); self::assertCount(11, $names); } public function testClearOverridesAndOverridesShareTheSameBook(): void { $assignment = $this->assignment(); $local = [ 'modulePreset' => ['bl00000001'], 'title' => ['decoration' => ['font' => ['font' => ['desktop' => ['value' => ['weight' => '400']]]]]], ]; self::assertSame( ['bl00000001' => ['title.decoration.font.font|desktop|value|weight']], $assignment->overrides()->find($local) ); self::assertSame($assignment->overrides(), $assignment->overrides()); self::assertSame(['modulePreset' => ['bl00000001']], $assignment->clearOverrides($local)); } }