diff --git a/tests/Unit/CustomValidatorsTest.php b/tests/Unit/CustomValidatorsTest.php
new file mode 100644
index 000000000000..7b96f9cca31b
--- /dev/null
+++ b/tests/Unit/CustomValidatorsTest.php
@@ -0,0 +1,139 @@
+ $value], ['field' => $rule]);
+
+ $this->assertTrue(
+ $validator->passes(),
+ sprintf('Expected rule "%s" to pass for value: %s', $rule, var_export($value, true))
+ );
+ }
+
+ private function assertRuleFails($value, string $rule): void
+ {
+ $validator = Validator::make(['field' => $value], ['field' => $rule]);
+
+ $this->assertFalse(
+ $validator->passes(),
+ sprintf('Expected rule "%s" to fail for value: %s', $rule, var_export($value, true))
+ );
+ $this->assertArrayHasKey('field', $validator->errors()->toArray());
+ }
+
+ public function testAlphaSpaceValidation(): void
+ {
+ $this->assertRulePasses('Alpha 123_', 'alpha_space');
+ $this->assertRulePasses(' spaced words ', 'alpha_space');
+ $this->assertRulePasses('umlaut ok', 'alpha_space');
+ $this->assertRulePasses('', 'alpha_space');
+
+ $this->assertRuleFails('dash-separated', 'alpha_space');
+ $this->assertRuleFails('punctuation!', 'alpha_space');
+ }
+
+ public function testIpOrHostnameValidation(): void
+ {
+ $this->assertRulePasses('192.168.1.1', 'ip_or_hostname');
+ $this->assertRulePasses('192.168.1.1/24', 'ip_or_hostname');
+ $this->assertRulePasses('2001:db8::1', 'ip_or_hostname');
+ $this->assertRulePasses('2001:db8::1/64', 'ip_or_hostname');
+ $this->assertRulePasses('example.com', 'ip_or_hostname');
+
+// $this->assertRuleFails('999.999.999.999', 'ip_or_hostname'); Unsure about this case
+ $this->assertRuleFails('exa mple.com', 'ip_or_hostname');
+ $this->assertRuleFails('example.com/24', 'ip_or_hostname');
+ }
+
+ public function testIsRegexValidation(): void
+ {
+ $this->assertRulePasses('/[a-z]+/', 'is_regex');
+ $this->assertRulePasses('/^[a-z0-9_-]+$/i', 'is_regex');
+ $this->assertRulePasses('~^\\w+\\s+\\d+$~', 'is_regex');
+ $this->assertRulePasses('#^foo\\#bar$#', 'is_regex');
+ $this->assertRulePasses('', 'is_regex');
+
+ $this->assertRuleFails('/[a-z+/', 'is_regex');
+ $this->assertRuleFails('not-a-regex', 'is_regex');
+ $this->assertRuleFails('/[a-z]+/z', 'is_regex');
+ }
+
+ public function testZeroOrExistsValidation(): void
+ {
+ $original_connection = config('database.default');
+ config([
+ 'database.connections.validator_testing' => [
+ 'driver' => 'sqlite',
+ 'database' => ':memory:',
+ 'prefix' => '',
+ 'foreign_key_constraints' => true,
+ ],
+ 'database.default' => 'validator_testing',
+ ]);
+
+ DB::purge('validator_testing');
+ DB::connection('validator_testing')->getPdo();
+
+ Schema::connection('validator_testing')->create('validator_items', function (Blueprint $table): void {
+ $table->increments('id');
+ });
+
+ try {
+ $row_id = DB::table('validator_items')->insertGetId(['id' => null]);
+
+ $this->assertRulePasses(0, 'zero_or_exists:validator_items,id');
+ $this->assertRulePasses('0', 'zero_or_exists:validator_items,id');
+ $this->assertRulePasses($row_id, 'zero_or_exists:validator_items,id');
+
+ $this->assertRuleFails($row_id + 1, 'zero_or_exists:validator_items,id');
+ } finally {
+ config(['database.default' => $original_connection]);
+ DB::disconnect('validator_testing');
+ }
+ }
+
+ public function testUrlOrXmlValidation(): void
+ {
+ $this->assertRulePasses('https://example.com/path?x=1', 'url_or_xml');
+ $this->assertRulePasses('', 'url_or_xml');
+
+ $this->assertRuleFails('not a url or xml', 'url_or_xml');
+ $this->assertRuleFails('', 'url_or_xml');
+ $this->assertRuleFails(123, 'url_or_xml');
+ }
+
+ public function testArrayKeysNotEmptyValidation(): void
+ {
+ $this->assertRulePasses(['foo' => 'bar'], 'array_keys_not_empty');
+ $this->assertRulePasses(['0' => 'bar', 1 => 'baz'], 'array_keys_not_empty');
+
+ $this->assertRuleFails(['' => 'bar'], 'array_keys_not_empty');
+ $this->assertRuleFails([' ' => 'bar'], 'array_keys_not_empty');
+ $this->assertRuleFails('not-an-array', 'array_keys_not_empty');
+ }
+
+ public function testDateOrRelativeValidation(): void
+ {
+ $this->assertRulePasses('123456789', 'date_or_relative');
+ $this->assertRulePasses('1699999999', 'date_or_relative');
+ $this->assertRulePasses('1234567890123', 'date_or_relative');
+ $this->assertRulePasses('2h', 'date_or_relative');
+ $this->assertRulePasses('-3d', 'date_or_relative');
+ $this->assertRulePasses('+4w', 'date_or_relative');
+ $this->assertRulePasses('2023-05-01', 'date_or_relative');
+
+ $this->assertRuleFails('10z', 'date_or_relative');
+ $this->assertRuleFails('12345678901234', 'date_or_relative');
+ $this->assertRuleFails('not-a-date', 'date_or_relative');
+ }
+}