*/ private function definitions(): array { return [ 'role' => new FieldDefinition('role', 'Funktion', 'string', 'pods', 'writable', true), 'level' => new FieldDefinition('level', 'Stufe', 'choice', 'pods', 'writable', false, false, [ ['value' => 'junior', 'label' => 'Junior'], ['value' => 'senior', 'label' => 'Senior'], ]), 'mentor' => new FieldDefinition('mentor', 'Mentor', 'relation', 'pods', 'writable', false, false, null, [ 'object_type' => 'post_type', 'name' => 'lit_person', ]), 'photos' => new FieldDefinition('photos', 'Fotos', 'file', 'pods', 'writable', false, true, null, [ 'object_type' => 'media', 'name' => null, ]), 'start' => new FieldDefinition('start', 'Beginn', 'date', 'pods', 'writable'), 'secret' => new FieldDefinition('_secret', '_secret', 'string', 'meta', 'readonly'), 'city' => new FieldDefinition('city', 'city', 'unknown', 'sample', 'unknown'), ]; } public function testValidValuesPassUnchanged(): void { Functions\when('get_post_type')->alias(static fn (int $id) => [12 => 'lit_person', 3 => 'attachment'][$id] ?? false); $values = ['role' => 'Leitung', 'level' => 'senior', 'mentor' => 12, 'photos' => [3], 'start' => '2026-10-01']; $result = (new FieldValidator())->validate($this->definitions(), $values, true); self::assertSame([], $result['errors']); self::assertSame($values, $result['values']); } public function testRequiredFieldMustBeSetOnCreateAndNotClearedOnUpdate(): void { $validator = new FieldValidator(); self::assertSame('Pflichtfeld fehlt.', $validator->validate($this->definitions(), [], true)['errors']['role']); self::assertSame( 'Pflichtfeld darf nicht geleert werden.', $validator->validate($this->definitions(), ['role' => ''], false)['errors']['role'] ); self::assertSame([], $validator->validate($this->definitions(), ['level' => 'junior'], false)['errors']); } public function testUnknownReadonlyAndInvalidValuesAreReported(): void { Functions\when('get_post_type')->alias(static fn (int $id) => [7 => 'page', 3 => 'attachment'][$id] ?? false); $errors = (new FieldValidator())->validate($this->definitions(), [ 'role' => 'x', 'nope' => 1, 'secret' => 'x', 'level' => 'expert', 'mentor' => 7, 'photos' => 3, 'start' => '01.10.2026', ], false)['errors']; self::assertStringContainsString('lit-content-types', $errors['nope']); self::assertSame('Feld ist schreibgeschützt.', $errors['secret']); self::assertSame('Erlaubte Werte: junior, senior.', $errors['level']); self::assertSame('Eintrag 7 ist vom Typ page, erwartet lit_person.', $errors['mentor']); self::assertSame('Liste erwartet.', $errors['photos']); self::assertSame('Datum im Format JJJJ-MM-TT erwartet.', $errors['start']); self::assertArrayNotHasKey('role', $errors); } public function testMissingRelatedPostAndNonMediaAreRejected(): void { Functions\when('get_post_type')->alias(static fn (int $id) => [5 => 'page'][$id] ?? false); $errors = (new FieldValidator())->validate($this->definitions(), ['mentor' => 99, 'photos' => [5]], false)['errors']; self::assertSame('Eintrag 99 existiert nicht.', $errors['mentor']); self::assertSame('5 ist kein Medium der Mediathek.', $errors['photos']); } public function testUnknownTypedFieldsAreAcceptedWithAWarningAndNullClears(): void { $result = (new FieldValidator())->validate($this->definitions(), ['city' => ['any' => 'thing'], 'level' => null], false); self::assertSame([], $result['errors']); self::assertSame(['city' => ['any' => 'thing'], 'level' => null], $result['values']); self::assertCount(1, $result['warnings']); self::assertStringContainsString('city', $result['warnings'][0]); } }