mirror of
https://github.com/filamentphp/filament.git
synced 2026-09-21 05:20:26 +08:00
fix: FileUpload requiredIf() (#18818)
This commit is contained in:
Generated
+1
-26
@@ -139,7 +139,6 @@
|
||||
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.5",
|
||||
@@ -3523,7 +3522,6 @@
|
||||
"integrity": "sha512-iUelgiTMgPVMpY5ZqASUpk8mC8HuR9FWKaDzK27w9oWip9tuB54Z8mePTxNcQaSPb6ErzEaC8x8egrRt7OsdGQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
@@ -3849,7 +3847,6 @@
|
||||
"integrity": "sha512-LcH9KE4QBUJ6IPwt1Uo5iU7zatFjUUvXbctIu2fKQ9nqJ7nNSFxRhkNyporVFkTWYH7/rb0qMoF1VxSUGefG5w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
@@ -3964,7 +3961,6 @@
|
||||
"integrity": "sha512-i7O0ptSibEtTy+2PIPsNKEvhTvMaFJg1W4Oxfnbuxvaigs7cJV9Q0lwDUcc7CPsNw2T1+44wcxg431CzTvdYoA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
@@ -3980,7 +3976,6 @@
|
||||
"integrity": "sha512-WKR4ucALq+lwx0WJZW17CspeTpXorbIOpvKv5mulZica6QxqfMhn8n1IXCkDws/mCoLRx4Drk5d377tIjFNsvQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"prosemirror-changeset": "^2.3.0",
|
||||
"prosemirror-collab": "^1.3.1",
|
||||
@@ -4012,7 +4007,6 @@
|
||||
"integrity": "sha512-IXNvyLITpPiuXHn/q1ntztPYJZMFjPAokKj+OQz3MFNYlzAX3I409KD/EwwCubisRIAFiNX0ZjIIXxxZ3AhFTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
@@ -4718,7 +4712,6 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -4745,7 +4738,6 @@
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
@@ -5404,7 +5396,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -5600,7 +5591,6 @@
|
||||
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
},
|
||||
@@ -7020,7 +7010,6 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
@@ -7483,8 +7472,7 @@
|
||||
"resolved": "https://registry.npmjs.org/filepond/-/filepond-4.32.10.tgz",
|
||||
"integrity": "sha512-uflpIL+rfAgsYk7HgXugh7ELlHNHLTPtIHfvuq0gv3x2qNfVxdGA6T7RPxMh9Vji8ysXstXkDHVXQ4hY38MPTQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/filepond-plugin-file-validate-size": {
|
||||
"version": "2.2.8",
|
||||
@@ -8501,7 +8489,6 @@
|
||||
"integrity": "sha512-33AmZ+xjZhg2JMCe+vDf6a9mzWukE7l+wAtesjE7KyteqqKjzxv7aVQeWnul1Ve26mWvEQqyPwl0OctNBfSR9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"file-type": "^12.0.0",
|
||||
"globby": "^10.0.0",
|
||||
@@ -9844,7 +9831,6 @@
|
||||
"integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
@@ -11052,7 +11038,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -11665,7 +11650,6 @@
|
||||
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
@@ -11926,7 +11910,6 @@
|
||||
"integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"orderedmap": "^2.0.0"
|
||||
}
|
||||
@@ -11959,7 +11942,6 @@
|
||||
"integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"prosemirror-model": "^1.0.0",
|
||||
"prosemirror-transform": "^1.0.0",
|
||||
@@ -12012,7 +11994,6 @@
|
||||
"integrity": "sha512-WkKgnyjNncri03Gjaz3IFWvCAE94XoiEgvtr0/r2Xw7R8/IjK3sKLSiDoCHWcsXSAinVaKlGRZDvMCsF1kbzjA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"prosemirror-model": "^1.20.0",
|
||||
"prosemirror-state": "^1.0.0",
|
||||
@@ -13785,7 +13766,6 @@
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
@@ -14371,7 +14351,6 @@
|
||||
"integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.7",
|
||||
"@types/estree": "^1.0.8",
|
||||
@@ -14421,7 +14400,6 @@
|
||||
"integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@discoveryjs/json-ext": "^0.5.0",
|
||||
"@webpack-cli/configtest": "^1.2.0",
|
||||
@@ -14558,7 +14536,6 @@
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
@@ -14676,7 +14653,6 @@
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
@@ -14779,7 +14755,6 @@
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
|
||||
@@ -77,6 +77,24 @@ class BaseFileUpload extends Field implements Contracts\HasNestedRecursiveValida
|
||||
|
||||
protected ?Closure $saveUploadedFileUsing = null;
|
||||
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
protected const ARRAY_VALIDATION_RULES = [
|
||||
'filled',
|
||||
'prohibited',
|
||||
'prohibited_if',
|
||||
'prohibited_unless',
|
||||
'required_if',
|
||||
'required_if_accepted',
|
||||
'required_if_declined',
|
||||
'required_unless',
|
||||
'required_with',
|
||||
'required_with_all',
|
||||
'required_without',
|
||||
'required_without_all',
|
||||
];
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
@@ -598,7 +616,23 @@ class BaseFileUpload extends Field implements Contracts\HasNestedRecursiveValida
|
||||
$rules[] = "min:{$count}";
|
||||
}
|
||||
|
||||
$rules[] = function (string $attribute, array $value, Closure $fail): void {
|
||||
$arrayRules = [];
|
||||
$fileRules = [];
|
||||
|
||||
foreach (parent::getValidationRules() as $rule) {
|
||||
if ($this->isArrayValidationRule($rule)) {
|
||||
$arrayRules[] = $rule;
|
||||
} else {
|
||||
$fileRules[] = $rule;
|
||||
}
|
||||
}
|
||||
|
||||
$rules = [
|
||||
...$rules,
|
||||
...$arrayRules,
|
||||
];
|
||||
|
||||
$rules[] = function (string $attribute, array $value, Closure $fail) use ($fileRules): void {
|
||||
$files = array_filter($value, fn (TemporaryUploadedFile | string $file): bool => $file instanceof TemporaryUploadedFile);
|
||||
|
||||
$name = $this->getName();
|
||||
@@ -607,7 +641,7 @@ class BaseFileUpload extends Field implements Contracts\HasNestedRecursiveValida
|
||||
|
||||
$validator = Validator::make(
|
||||
[$name => $files],
|
||||
["{$name}.*" => ['file', ...parent::getValidationRules()]],
|
||||
["{$name}.*" => ['file', ...$fileRules]],
|
||||
$validationMessages ? ["{$name}.*" => $validationMessages] : [],
|
||||
["{$name}.*" => $this->getValidationAttribute()],
|
||||
);
|
||||
@@ -622,6 +656,17 @@ class BaseFileUpload extends Field implements Contracts\HasNestedRecursiveValida
|
||||
return $rules;
|
||||
}
|
||||
|
||||
protected function isArrayValidationRule(mixed $rule): bool
|
||||
{
|
||||
if (! is_string($rule)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ruleName = strtolower(explode(':', $rule)[0]);
|
||||
|
||||
return in_array($ruleName, static::ARRAY_VALIDATION_RULES, strict: true);
|
||||
}
|
||||
|
||||
#[ExposedLivewireMethod]
|
||||
#[Renderless]
|
||||
public function deleteUploadedFile(string $fileKey): static
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Filament\Forms\Components\Field;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tests\Fixtures\Livewire\Livewire;
|
||||
use Filament\Tests\TestCase;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Livewire\Exceptions\RootTagMissingFromViewException;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
@@ -81,6 +83,118 @@ describe('visibility', function (): void {
|
||||
});
|
||||
});
|
||||
|
||||
describe('validation', function (): void {
|
||||
it('can use `requiredIf()` and fails validation when condition is met', function (): void {
|
||||
$rules = [];
|
||||
$errors = [];
|
||||
|
||||
try {
|
||||
Schema::make(Livewire::make())
|
||||
->statePath('data')
|
||||
->components([
|
||||
$field1 = (new Field('type'))
|
||||
->default('file'),
|
||||
$field2 = FileUpload::make('document')
|
||||
->requiredIf('type', 'file'),
|
||||
])
|
||||
->fill()
|
||||
->validate();
|
||||
} catch (ValidationException $exception) {
|
||||
$rules = array_keys($exception->validator->failed()[$field2->getStatePath()] ?? []);
|
||||
$errors = $exception->validator->errors()->get($field2->getStatePath());
|
||||
}
|
||||
|
||||
expect($rules)
|
||||
->toContain('RequiredIf');
|
||||
|
||||
expect($errors)
|
||||
->toContain('The document field is required when type is file.');
|
||||
});
|
||||
|
||||
it('can use `requiredIf()` and passes validation when condition is not met', function (): void {
|
||||
$validationPassed = false;
|
||||
|
||||
try {
|
||||
Schema::make(Livewire::make())
|
||||
->statePath('data')
|
||||
->components([
|
||||
(new Field('type'))
|
||||
->default('text'),
|
||||
FileUpload::make('document')
|
||||
->requiredIf('type', 'file'),
|
||||
])
|
||||
->fill()
|
||||
->validate();
|
||||
|
||||
$validationPassed = true;
|
||||
} catch (ValidationException) {
|
||||
$validationPassed = false;
|
||||
}
|
||||
|
||||
expect($validationPassed)->toBeTrue();
|
||||
});
|
||||
|
||||
it('can use `requiredUnless()` and fails validation when condition is not met', function (): void {
|
||||
$rules = [];
|
||||
$errors = [];
|
||||
|
||||
try {
|
||||
Schema::make(Livewire::make())
|
||||
->statePath('data')
|
||||
->components([
|
||||
$field1 = (new Field('type'))
|
||||
->default('text'),
|
||||
$field2 = FileUpload::make('document')
|
||||
->requiredUnless('type', 'file'),
|
||||
])
|
||||
->fill()
|
||||
->validate();
|
||||
} catch (ValidationException $exception) {
|
||||
$rules = array_keys($exception->validator->failed()[$field2->getStatePath()] ?? []);
|
||||
$errors = $exception->validator->errors()->get($field2->getStatePath());
|
||||
}
|
||||
|
||||
expect($rules)
|
||||
->toContain('RequiredUnless');
|
||||
|
||||
expect($errors)
|
||||
->toContain('The document field is required unless type is in file.');
|
||||
});
|
||||
|
||||
it('can use `requiredUnless()` and passes validation when condition is met', function (): void {
|
||||
$validationPassed = false;
|
||||
|
||||
try {
|
||||
Schema::make(Livewire::make())
|
||||
->statePath('data')
|
||||
->components([
|
||||
(new Field('type'))
|
||||
->default('file'),
|
||||
FileUpload::make('document')
|
||||
->requiredUnless('type', 'file'),
|
||||
])
|
||||
->fill()
|
||||
->validate();
|
||||
|
||||
$validationPassed = true;
|
||||
} catch (ValidationException) {
|
||||
$validationPassed = false;
|
||||
}
|
||||
|
||||
expect($validationPassed)->toBeTrue();
|
||||
});
|
||||
|
||||
it('applies `rule()` to individual files, not the array', function (): void {
|
||||
$field = FileUpload::make('document')
|
||||
->rule('mimetypes:image/png');
|
||||
|
||||
$rules = $field->getValidationRules();
|
||||
|
||||
$stringRules = array_filter($rules, fn ($rule) => is_string($rule));
|
||||
expect($stringRules)->not->toContain('mimetypes:image/png');
|
||||
});
|
||||
});
|
||||
|
||||
class TestComponentWithFileUpload extends Livewire
|
||||
{
|
||||
public function form(Schema $form): Schema
|
||||
|
||||
Reference in New Issue
Block a user