feat: add new validators, Flutter form integration, example app, and CI#2
Conversation
…ir tests, and correct a test filename.
…on, improve test coverage, and fix regex edge cases.
… the license copyright year.
📝 WalkthroughWalkthroughAdds v1.1.0: a new Flutter Changes
Sequence Diagram(s)(omitted) Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 16
🧹 Nitpick comments (9)
example/linux/runner/my_application.cc (2)
104-116: Remove stale placeholder comments in lifecycle hooks.The commented
selflines and generic placeholder text in startup/shutdown add noise without conveying actionable behavior.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example/linux/runner/my_application.cc` around lines 104 - 116, Remove the stale placeholder comments in the lifecycle hooks: delete the commented "MyApplication* self = MY_APPLICATION(object);" lines and the generic "Perform any actions required..." comments inside my_application_startup and my_application_shutdown; if specific startup/shutdown work is required, replace these placeholders with the actual calls or TODO comments referencing MyApplication, otherwise leave the functions clean and only call the parent handlers via G_APPLICATION_CLASS(my_application_parent_class)->startup(application) and G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application) respectively.
87-88: Clear previous duplicated argv before reassigning.Line 87 overwrites
self->dart_entrypoint_argumentsdirectly. Clearing prior storage first avoids leaking memory if this path is hit more than once.Suggested patch
static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { MyApplication* self = MY_APPLICATION(application); // Strip out the first argument as it is the binary name. + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example/linux/runner/my_application.cc` around lines 87 - 88, The code overwrites self->dart_entrypoint_arguments with g_strdupv(*arguments + 1) without freeing any previous value, leaking memory if executed multiple times; before assigning, check if self->dart_entrypoint_arguments is non-null and free the previous argv using g_strfreev(self->dart_entrypoint_arguments), then set self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); keep the same allocation call (g_strdupv) and do this cleanup in the same scope where the assignment occurs in my_application.cc.test/flutter_validators_test.dart (1)
1-1: Consider removing or repurposing this empty test file.This file contains only an empty
main()function. If tests have been moved to more specific files (e.g.,test/form_validator_test.dart,test/validators/*_test.dart), consider removing this file to avoid confusion about where tests live.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/flutter_validators_test.dart` at line 1, This file contains only an empty main() test entrypoint; either delete this test file or repurpose it by adding meaningful tests (e.g., move or consolidate tests from form_validator_test.dart or validators/*_test.dart into this file) so it is not a no-op; locate the empty main() in flutter_validators_test.dart and either remove the file or replace the empty main() with the appropriate test group(s) that exercise your validator functions.test/validators/length_test.dart (1)
6-16: Consider adding edge-case coverage.The current tests cover basic valid and invalid scenarios. Consider adding tests for edge cases such as empty strings, single-character strings, boundary conditions with equal min/max, and potentially invalid inputs (e.g., min > max, negative values) to ensure robustness.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/validators/length_test.dart` around lines 6 - 16, Add tests in test/validators/length_test.dart around the existing isLength cases to cover edge cases: assert behavior for empty string (''.isLength(0), ''.isLength(0,0)), single-character string ('a'.isLength(1)), boundary conditions where min == max (e.g., 'a'.isLength(1,1) and 'ab'.isLength(2,2)), invalid constraint cases such as min > max (e.g., expect('a'.isLength(2,1), isFalse or the function's defined behavior)), and negative values for min/max to verify handling; reference the existing test group names and the isLength(String) extension/function to add these assertions so behavior is fully specified.test/validators/base32_test.dart (1)
12-17: Nice coverage; consider one padding-placement edge case.Line 12–17 could also assert that malformed
=placement is rejected (not just length/charset), to guard regex regressions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/validators/base32_test.dart` around lines 12 - 17, In the test block named 'Invalid base32' add assertions that strings with incorrectly placed or malformed padding are rejected by the isBase32 check; for example add expects like expect('JB=SWY3DPEHPK3PX'.isBase32, isFalse), expect('=JBSWY3DPEHPK3PX'.isBase32, isFalse) and expect('JBSWY3DPEHPK3PX=='.isBase32, isFalse) so the isBase32 property is verified to reject '=' appearing in the middle, leading padding, or invalid padding counts.example/android/build.gradle.kts (1)
14-20: Consider merging the twosubprojectsblocks.The two separate
subprojectsblocks can be combined into one for improved readability.♻️ Suggested refactor
subprojects { val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) project.layout.buildDirectory.value(newSubprojectBuildDir) -} -subprojects { project.evaluationDependsOn(":app") }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example/android/build.gradle.kts` around lines 14 - 20, Merge the two subprojects blocks into a single block to improve readability: move the build directory configuration (val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) and project.layout.buildDirectory.value(newSubprojectBuildDir)) and the evaluation dependency call (project.evaluationDependsOn(":app")) into one subprojects { ... } scope so both configurations are applied together.test/form_validator_test.dart (1)
22-27: Consider adding a test fornullinput to the length validator.The
requiredandnullhandling, but thelengthvalidator tests don't verify behavior whennullis passed. This could help catch unexpected exceptions or ensure consistent null-handling across validators.🧪 Suggested additional test case
test('Length validator', () { final len = Validator.length(3, max: 5, errorMessage: 'Bad len'); expect(len('abc'), isNull); expect(len('ab'), equals('Bad len')); expect(len('abcdef'), equals('Bad len')); + expect(len(null), isNull); // or equals('Bad len') depending on expected behavior });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/form_validator_test.dart` around lines 22 - 27, Add a test that verifies Validator.length handles null input without throwing and returns the expected result (likely an error message or null depending on intended behavior); update the 'Length validator' test block (the test with final len = Validator.length(3, max: 5, errorMessage: 'Bad len')) to include an assertion for len(null) so null-handling for Validator.length is covered consistently with required and email validators.test/validators/uuid_test.dart (1)
6-11: Move the invalid UUID test case to the "Invalid UUIDs" group.Line 9 tests an invalid UUID (contains non-hex character 't') but is placed inside the "Valid UUIDs" test. This reduces test clarity and makes failures harder to diagnose.
♻️ Suggested refactor
test('Valid UUIDs', () { expect('123e4567-e89b-12d3-a456-426614174000'.isUUID, isTrue); expect('550e8400-e29b-41d4-a716-446655440000'.isUUID, isTrue); - expect('8bA4A1C9-5e19-4F5t-b5a8-44243b1E2D4A'.isUUID, isFalse); // invalid hex char 't' expect('8bA4A1C9-5e19-4F5b-b5a8-44243b1E2D4A'.isUUID, isTrue); }); test('Invalid UUIDs', () { expect('invalid-uuid'.isUUID, isFalse); expect('123e4567-e89b-12d3-a456-42661417400'.isUUID, isFalse); // Too short + expect('8bA4A1C9-5e19-4F5t-b5a8-44243b1E2D4A'.isUUID, isFalse); // Invalid hex char 't' expect(''.isUUID, isFalse); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/validators/uuid_test.dart` around lines 6 - 11, The test case asserting that '8bA4A1C9-5e19-4F5t-b5a8-44243b1E2D4A'.isUUID isFalse is currently inside the "Valid UUIDs" test; move that expect line into the "Invalid UUIDs" group so the invalid-case string (contains non-hex 't') is tested alongside other invalid samples, keeping the "Valid UUIDs" group reserved for only isTrue expectations and the "Invalid UUIDs" group for isFalse expectations.lib/validators/length.dart (1)
22-24: Consider validatingminandmaxparameters.The function doesn't validate that
min >= 0or thatmax >= minwhen both are provided. Invalid parameters likeisLength("abc", -1)orisLength("abc", 5, 3)will produce potentially confusing results rather than clear errors.🛡️ Suggested defensive validation
bool _isLength(String str, int min, [int? max]) { + assert(min >= 0, 'min must be non-negative'); + assert(max == null || max >= min, 'max must be >= min'); return str.length >= min && (max == null || str.length <= max); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/validators/length.dart` around lines 22 - 24, The _isLength function should defensively validate its parameters: ensure min is >= 0 and if max is provided ensure max >= min; if not, throw an ArgumentError (or RangeError) with a clear message. Update the _isLength(String str, int min, [int? max]) entry point to perform these checks before using str.length so invalid calls like isLength("a",-1) or isLength("a",5,3) fail fast and clearly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/test.yml:
- Line 13: Update the GitHub Actions step using the deprecated
actions/checkout@v3 to actions/checkout@v4: locate the workflow step referencing
"uses: actions/checkout@v3" in the test.yml and change the version tag to "@v4"
so the job uses the latest checkout action (no other code changes required
unless you pin a full SHA or adjust inputs for v4-specific options).
In `@example/ios/Runner.xcodeproj/project.pbxproj`:
- Line 369: The project file currently hard-codes the Apple developer team via
DEVELOPMENT_TEAM = W2B7PMH9SQ; in the Xcode project, which prevents external
contributors/CI from building; remove those DEVELOPMENT_TEAM assignments from
the committed project.pbxproj (the occurrences you saw) or instead move them
into a local/user-specific xcconfig that is not committed (and reference that
xcconfig in the project), and update the project to use the unspecified/blank
team setting so builds default to the local developer/CI team. Ensure you remove
all instances of the DEVELOPMENT_TEAM = W2B7PMH9SQ; entries (the three
occurrences flagged) or replace them with an xcconfig mechanism and add that
xcconfig to .gitignore.
In `@example/pubspec.yaml`:
- Around line 21-23: The example's pubspec "environment" block currently sets
"sdk: ^3.11.3" which conflicts with the root package's SDK floor of "^3.7.0";
update the example's pubspec.yaml to align these constraints by either lowering
the example "sdk" floor to "^3.7.0" (if compatible) or raising the root package
SDK floor to "^3.11.3" so both match, or add a brief comment in the example's
pubspec explaining why the higher floor is required; look for the "environment"
-> "sdk: ^3.11.3" entry to change.
In `@example/test/widget_test.dart`:
- Around line 14-28: Replace the failing counter test inside the
testWidgets('Counter increments smoke test', ...) block with a form-validator
test: pump the MyApp widget (MyApp), locate the TextFormField widgets (use their
Keys or hintText/labelText for email, url, credit card, numeric fields), enter
invalid values via tester.enterText for each field, tap the form submit/validate
button via tester.tap (or call tester.testTextInput.receiveAction if form
validates on field), await tester.pump(), and assert that the appropriate
validation error text widgets appear (expect(find.text('Enter a valid email'),
findsOneWidget) etc. Then enter valid inputs, tap submit again, pump, and assert
that the error texts are gone and success state (e.g., success message or
absence of error widgets) is present; replace references to Icons.add and
find.text('0'/'1') with the form field interactions above.
In `@example/windows/runner/utils.cpp`:
- Around line 13-18: The condition checks for freopen_s are inverted and the
stderr duplication uses the wrong file pointer; change the logic so _dup2 is
called when freopen_s returns 0 (success) and for the stderr branch use
_fileno(stderr) when duplicating: update the two if(...) checks that call
freopen_s(&unused, "CONOUT$", "w", stdout/stderr) to test for == 0 and call
_dup2(_fileno(stdout), 1) for stdout and _dup2(_fileno(stderr), 2) for stderr
(symbols: freopen_s, _dup2, _fileno, stdout, stderr, unused).
- Around line 48-56: The code subtracts 1 from the unsigned result of
WideCharToMultiByte into target_length, which underflows when
WideCharToMultiByte returns 0; change the logic in the WideCharToMultiByte call
handling (around target_length, utf16_string, and utf8_string) to first capture
the return value into a signed/checked variable (e.g., int required =
::WideCharToMultiByte(...)); if required <= 0 return empty string; otherwise
compute target_length = required - 1 safely (or use size_t after checking), then
proceed to resize utf8_string using that safe length; update any checks that
relied on target_length == 0 accordingly.
In `@example/windows/runner/win32_window.cpp`:
- Around line 118-121: Destructor Win32Window::~Win32Window currently calls
Destroy(), which can re-enter Destroy() via WM_DESTROY and cause OnDestroy()
(e.g., FlutterWindow::OnDestroy()) to run multiple times; make the destroy path
idempotent by adding a guard (e.g., bool destroying_ or destroyed_) as a member,
check-and-set it at the start of Win32Window::Destroy() and in the destructor to
ensure Destroy() and OnDestroy() execute only once, or alternatively split the
one-time teardown (OnDestroy()) into a separate method invoked only when the
guard indicates first-time destruction; update all call sites
(Win32Window::~Win32Window, Destroy(), WM_DESTROY handler, and OnDestroy()) to
use the guard so double-teardown cannot happen.
In `@lib/form_validator.dart`:
- Around line 114-117: In _build, optional validators treat whitespace-only
strings as non-empty; change the emptiness check to treat strings with only
whitespace as empty by trimming the input before checking: in the closure
returned by _build (function _build), replace the current if (value == null ||
value.isEmpty) check with a check that uses value.trim().isEmpty so that
whitespace-only input returns null (letting required handle emptiness) and only
non-blank strings are passed to the provided test function.
In `@lib/validators/base32.dart`:
- Around line 40-42: The current validator rejects valid unpadded Base32 and
allows arbitrary padding; remove the strict length % 8 check and instead
validate padding correctly: use a tighter regex like /^[A-Z2-7]+={0,6}$/ to
ensure only trailing '=' are allowed, then if there are padding characters
enforce that the total length is a multiple of 8 and that the pad count is one
of the valid values (1, 3, 4, or 6); update the function that returns the
boolean (the code using str.length % 8 and RegExp(r'^[A-Z2-7]+=*$')) to
implement these rules so unpadded inputs pass and invalid padding layouts are
rejected.
In `@lib/validators/ip.dart`:
- Around line 31-35: The _isIP function currently treats any unsupported numeric
version as a signal to fallback to IPv4/IPv6 checks; change its logic so only
null/undefined version triggers the fallback, while explicit unsupported
versions return false. Concretely, in _isIP (and any wrapper isIP if present)
keep the checks for version == 4 and version == 6, perform the IPv4/IPv6 OR only
when version is null (or omitted), and return false for any other explicit
version values to avoid incorrectly accepting e.g. version == 5.
In `@lib/validators/numeric.dart`:
- Around line 23-25: The _isNumeric(String str) validator should reject
non-finite values; change its logic to call double.tryParse(str), store the
result in a local variable (e.g., var v), and return true only when v != null &&
v.isFinite so that "NaN" and "Infinity" are treated as invalid inputs.
In `@lib/validators/uuid.dart`:
- Around line 1-3: The UUID validator currently accepts any hex nibble for the
version and variant; update the validation regex used in the UUID validator to
strictly require the version nibble to be one of 1,3,4,5 and the variant nibble
to be one of 8,9,A,B (case-insensitive). Locate the UUID validation regex in the
validator function (the regex that matches the third and fourth hyphen-separated
groups), change the version group to [1345] and the variant group to [89ABab]
(or use case-insensitive flag), keep the overall 8-4-4-4-12 structure and
anchors, and add/adjust unit tests to cover valid versions 1/3/4/5 and invalid
versions/variants.
In `@README.md`:
- Around line 21-26: The README currently claims "20+ validators" but the
validators table documents only 19 entries; either update the hero copy to "19
validators" (and adjust the repeated "20+ validators" phrases) or add the
missing validator row(s) to the validators table so the count is accurate;
inspect the library's exported validator functions (and the built-in Validator
class/String extensions referenced in the README) to determine the true number,
then update the hero text and any other occurrences of "20+ validators" to match
that authoritative count or expand the table to include the missing
validator(s).
- Line 16: The license badge's anchor (<a href="...">) currently links to the
badge image URL; update the anchor's href to point to the repository LICENSE
file (for example "LICENSE" or "./LICENSE" or the absolute LICENSE URL) so
clicking the badge opens the actual license document instead of the image;
locate the anchor element that wraps the badge image and replace its href value
accordingly.
In `@test/validators/date_test.dart`:
- Around line 12-16: The assertion for '2023-13-01' in the "Invalid Dates" test
is inconsistent with its placement; inspect the intended behavior of the isDate
validator and either (a) if isDate should validate semantic calendar dates,
change the expectation in the 'Invalid Dates' test from isTrue to isFalse for
'2023-13-01', or (b) if isDate intentionally accepts DateTime.tryParse-style
rollover, move the '2023-13-01' expect(...) line out of the "Invalid Dates" test
into the "Valid Dates" test block so the test name matches the assertion; update
the test referring to the 'isDate' property accordingly.
In `@test/validators/int_test.dart`:
- Around line 13-18: The test named 'Invalid integers' contains a contradictory
expectation: it asserts '123 '.isInt as isTrue inside an invalid-case block;
update the assertion to reflect the test intent by either moving the '123
'.isInt expectation into the valid integers test or flipping it to isFalse in
this test. Locate the test function labeled "Invalid integers" in
test/validators/int_test.dart and adjust the expect call for the '123 ' input
(the isInt check) so the expectation matches the test purpose.
---
Nitpick comments:
In `@example/android/build.gradle.kts`:
- Around line 14-20: Merge the two subprojects blocks into a single block to
improve readability: move the build directory configuration (val
newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) and
project.layout.buildDirectory.value(newSubprojectBuildDir)) and the evaluation
dependency call (project.evaluationDependsOn(":app")) into one subprojects { ...
} scope so both configurations are applied together.
In `@example/linux/runner/my_application.cc`:
- Around line 104-116: Remove the stale placeholder comments in the lifecycle
hooks: delete the commented "MyApplication* self = MY_APPLICATION(object);"
lines and the generic "Perform any actions required..." comments inside
my_application_startup and my_application_shutdown; if specific startup/shutdown
work is required, replace these placeholders with the actual calls or TODO
comments referencing MyApplication, otherwise leave the functions clean and only
call the parent handlers via
G_APPLICATION_CLASS(my_application_parent_class)->startup(application) and
G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application)
respectively.
- Around line 87-88: The code overwrites self->dart_entrypoint_arguments with
g_strdupv(*arguments + 1) without freeing any previous value, leaking memory if
executed multiple times; before assigning, check if
self->dart_entrypoint_arguments is non-null and free the previous argv using
g_strfreev(self->dart_entrypoint_arguments), then set
self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); keep the same
allocation call (g_strdupv) and do this cleanup in the same scope where the
assignment occurs in my_application.cc.
In `@lib/validators/length.dart`:
- Around line 22-24: The _isLength function should defensively validate its
parameters: ensure min is >= 0 and if max is provided ensure max >= min; if not,
throw an ArgumentError (or RangeError) with a clear message. Update the
_isLength(String str, int min, [int? max]) entry point to perform these checks
before using str.length so invalid calls like isLength("a",-1) or
isLength("a",5,3) fail fast and clearly.
In `@test/flutter_validators_test.dart`:
- Line 1: This file contains only an empty main() test entrypoint; either delete
this test file or repurpose it by adding meaningful tests (e.g., move or
consolidate tests from form_validator_test.dart or validators/*_test.dart into
this file) so it is not a no-op; locate the empty main() in
flutter_validators_test.dart and either remove the file or replace the empty
main() with the appropriate test group(s) that exercise your validator
functions.
In `@test/form_validator_test.dart`:
- Around line 22-27: Add a test that verifies Validator.length handles null
input without throwing and returns the expected result (likely an error message
or null depending on intended behavior); update the 'Length validator' test
block (the test with final len = Validator.length(3, max: 5, errorMessage: 'Bad
len')) to include an assertion for len(null) so null-handling for
Validator.length is covered consistently with required and email validators.
In `@test/validators/base32_test.dart`:
- Around line 12-17: In the test block named 'Invalid base32' add assertions
that strings with incorrectly placed or malformed padding are rejected by the
isBase32 check; for example add expects like expect('JB=SWY3DPEHPK3PX'.isBase32,
isFalse), expect('=JBSWY3DPEHPK3PX'.isBase32, isFalse) and
expect('JBSWY3DPEHPK3PX=='.isBase32, isFalse) so the isBase32 property is
verified to reject '=' appearing in the middle, leading padding, or invalid
padding counts.
In `@test/validators/length_test.dart`:
- Around line 6-16: Add tests in test/validators/length_test.dart around the
existing isLength cases to cover edge cases: assert behavior for empty string
(''.isLength(0), ''.isLength(0,0)), single-character string ('a'.isLength(1)),
boundary conditions where min == max (e.g., 'a'.isLength(1,1) and
'ab'.isLength(2,2)), invalid constraint cases such as min > max (e.g.,
expect('a'.isLength(2,1), isFalse or the function's defined behavior)), and
negative values for min/max to verify handling; reference the existing test
group names and the isLength(String) extension/function to add these assertions
so behavior is fully specified.
In `@test/validators/uuid_test.dart`:
- Around line 6-11: The test case asserting that
'8bA4A1C9-5e19-4F5t-b5a8-44243b1E2D4A'.isUUID isFalse is currently inside the
"Valid UUIDs" test; move that expect line into the "Invalid UUIDs" group so the
invalid-case string (contains non-hex 't') is tested alongside other invalid
samples, keeping the "Valid UUIDs" group reserved for only isTrue expectations
and the "Invalid UUIDs" group for isFalse expectations.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4cf69597-b587-4e51-b12d-0f583732fad1
⛔ Files ignored due to path filters (38)
assets/banner.pngis excluded by!**/*.pngexample/android/app/src/main/res/mipmap-hdpi/ic_launcher.pngis excluded by!**/*.pngexample/android/app/src/main/res/mipmap-mdpi/ic_launcher.pngis excluded by!**/*.pngexample/android/app/src/main/res/mipmap-xhdpi/ic_launcher.pngis excluded by!**/*.pngexample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.pngis excluded by!**/*.pngexample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.pngis excluded by!**/*.pngexample/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.pngis excluded by!**/*.pngexample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.pngis excluded by!**/*.pngexample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.pngis excluded by!**/*.pngexample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.pngis excluded by!**/*.pngexample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.pngis excluded by!**/*.pngexample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.pngis excluded by!**/*.pngexample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.pngis excluded by!**/*.pngexample/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.pngis excluded by!**/*.pngexample/pubspec.lockis excluded by!**/*.lockexample/web/favicon.pngis excluded by!**/*.pngexample/web/icons/Icon-192.pngis excluded by!**/*.pngexample/web/icons/Icon-512.pngis excluded by!**/*.pngexample/web/icons/Icon-maskable-192.pngis excluded by!**/*.pngexample/web/icons/Icon-maskable-512.pngis excluded by!**/*.pngexample/windows/runner/resources/app_icon.icois excluded by!**/*.ico
📒 Files selected for processing (130)
.github/workflows/test.ymlCHANGELOG.mdLICENSEREADME.mdexample/.gitignoreexample/.metadataexample/README.mdexample/analysis_options.yamlexample/android/.gitignoreexample/android/app/build.gradle.ktsexample/android/app/src/debug/AndroidManifest.xmlexample/android/app/src/main/AndroidManifest.xmlexample/android/app/src/main/kotlin/com/example/example/MainActivity.ktexample/android/app/src/main/res/drawable-v21/launch_background.xmlexample/android/app/src/main/res/drawable/launch_background.xmlexample/android/app/src/main/res/values-night/styles.xmlexample/android/app/src/main/res/values/styles.xmlexample/android/app/src/profile/AndroidManifest.xmlexample/android/build.gradle.ktsexample/android/gradle.propertiesexample/android/gradle/wrapper/gradle-wrapper.propertiesexample/android/settings.gradle.ktsexample/ios/.gitignoreexample/ios/Flutter/AppFrameworkInfo.plistexample/ios/Flutter/Debug.xcconfigexample/ios/Flutter/Release.xcconfigexample/ios/Runner.xcodeproj/project.pbxprojexample/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedataexample/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plistexample/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettingsexample/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcschemeexample/ios/Runner.xcworkspace/contents.xcworkspacedataexample/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plistexample/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettingsexample/ios/Runner/AppDelegate.swiftexample/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.jsonexample/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.jsonexample/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.mdexample/ios/Runner/Base.lproj/LaunchScreen.storyboardexample/ios/Runner/Base.lproj/Main.storyboardexample/ios/Runner/Info.plistexample/ios/Runner/Runner-Bridging-Header.hexample/ios/Runner/SceneDelegate.swiftexample/ios/RunnerTests/RunnerTests.swiftexample/lib/main.dartexample/linux/.gitignoreexample/linux/CMakeLists.txtexample/linux/flutter/CMakeLists.txtexample/linux/flutter/generated_plugin_registrant.ccexample/linux/flutter/generated_plugin_registrant.hexample/linux/flutter/generated_plugins.cmakeexample/linux/runner/CMakeLists.txtexample/linux/runner/main.ccexample/linux/runner/my_application.ccexample/linux/runner/my_application.hexample/macos/.gitignoreexample/macos/Flutter/Flutter-Debug.xcconfigexample/macos/Flutter/Flutter-Release.xcconfigexample/macos/Flutter/GeneratedPluginRegistrant.swiftexample/macos/Runner.xcodeproj/project.pbxprojexample/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plistexample/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcschemeexample/macos/Runner.xcworkspace/contents.xcworkspacedataexample/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plistexample/macos/Runner/AppDelegate.swiftexample/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.jsonexample/macos/Runner/Base.lproj/MainMenu.xibexample/macos/Runner/Configs/AppInfo.xcconfigexample/macos/Runner/Configs/Debug.xcconfigexample/macos/Runner/Configs/Release.xcconfigexample/macos/Runner/Configs/Warnings.xcconfigexample/macos/Runner/DebugProfile.entitlementsexample/macos/Runner/Info.plistexample/macos/Runner/MainFlutterWindow.swiftexample/macos/Runner/Release.entitlementsexample/macos/RunnerTests/RunnerTests.swiftexample/pubspec.yamlexample/test/widget_test.dartexample/web/index.htmlexample/web/manifest.jsonexample/windows/.gitignoreexample/windows/CMakeLists.txtexample/windows/flutter/CMakeLists.txtexample/windows/flutter/generated_plugin_registrant.ccexample/windows/flutter/generated_plugin_registrant.hexample/windows/flutter/generated_plugins.cmakeexample/windows/runner/CMakeLists.txtexample/windows/runner/Runner.rcexample/windows/runner/flutter_window.cppexample/windows/runner/flutter_window.hexample/windows/runner/main.cppexample/windows/runner/resource.hexample/windows/runner/runner.exe.manifestexample/windows/runner/utils.cppexample/windows/runner/utils.hexample/windows/runner/win32_window.cppexample/windows/runner/win32_window.hlib/flutter_validators.dartlib/form_validator.dartlib/validators/alpha.dartlib/validators/base32.dartlib/validators/base58.dartlib/validators/credit_card.dartlib/validators/date.dartlib/validators/hex_color.dartlib/validators/ip.dartlib/validators/length.dartlib/validators/numeric.dartlib/validators/url.dartlib/validators/uuid.dartpubspec.yamltest/flutter_validators_test.darttest/form_validator_test.darttest/validators/alpha_test.darttest/validators/ascii_test.darttest/validators/base32_test.darttest/validators/base58_test.darttest/validators/boolean_test.darttest/validators/credit_card_test.darttest/validators/date_test.darttest/validators/email_test.darttest/validators/equals_test.darttest/validators/hex_color_test.darttest/validators/int_test.darttest/validators/ip_test.darttest/validators/length_test.darttest/validators/numeric_test.darttest/validators/phone_test.darttest/validators/url_test.darttest/validators/uuid_test.dart
…nsistency, and refine numeric validation to include `isFinite` check.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
README.md (1)
21-26:⚠️ Potential issue | 🟡 MinorValidator count still claims "20+" but the table documents 19.
Lines 21 and 26 state "20+ validators," but the reference table (lines 105–126) lists 19 validators. Update the count to "19 validators" or add the missing validator(s) to reach 20+.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 21 - 26, The README's header text saying "20+ validators" is inconsistent with the validators table which lists 19 items; either change the header phrase "20+ validators" to "19 validators" (and update any other occurrences of "20+" in the README) or add the missing validator entry to the table so the total is truly 20+; locate the header string "20+ validators" and the validators table block and update them together (and update any related badge/summary lines) so the documented count matches the actual list.
🧹 Nitpick comments (1)
example/test/widget_test.dart (1)
18-22: Consider using more robust field selectors instead of positional indexing.Using
find.byType(TextFormField).at(n)is fragile—if the field order changes in the UI, this test would silently test the wrong fields. Finding fields by their label text or usingKeywidgets would make the test more resilient to layout changes.♻️ Example: find fields by label text
- final emailField = find.byType(TextFormField).at(0); - final urlField = find.byType(TextFormField).at(1); - final creditCardField = find.byType(TextFormField).at(2); - final numericField = find.byType(TextFormField).at(3); + final emailField = find.widgetWithText(TextFormField, 'Email').first; + final urlField = find.widgetWithText(TextFormField, 'URL').first; + final creditCardField = find.widgetWithText(TextFormField, 'Credit Card').first; + final numericField = find.widgetWithText(TextFormField, 'Age (Numeric)').first;Alternatively, assign
Keyvalues to eachTextFormFieldinmain.dartand usefind.byKey(...).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example/test/widget_test.dart` around lines 18 - 22, The test is using fragile positional selectors (find.byType(TextFormField).at(n)) for emailField, urlField, creditCardField, and numericField; update the test to locate fields more robustly by either assigning Keys to each TextFormField in the widget under test (and using find.byKey for emailField, urlField, creditCardField, numericField) or by using semantic/label-based selectors such as find.widgetWithText(TextFormField, '<label>') / find.text('<label>') to target each field by its visible label instead of index so reordering of inputs won't break the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@README.md`:
- Around line 21-26: The README's header text saying "20+ validators" is
inconsistent with the validators table which lists 19 items; either change the
header phrase "20+ validators" to "19 validators" (and update any other
occurrences of "20+" in the README) or add the missing validator entry to the
table so the total is truly 20+; locate the header string "20+ validators" and
the validators table block and update them together (and update any related
badge/summary lines) so the documented count matches the actual list.
---
Nitpick comments:
In `@example/test/widget_test.dart`:
- Around line 18-22: The test is using fragile positional selectors
(find.byType(TextFormField).at(n)) for emailField, urlField, creditCardField,
and numericField; update the test to locate fields more robustly by either
assigning Keys to each TextFormField in the widget under test (and using
find.byKey for emailField, urlField, creditCardField, numericField) or by using
semantic/label-based selectors such as find.widgetWithText(TextFormField,
'<label>') / find.text('<label>') to target each field by its visible label
instead of index so reordering of inputs won't break the test.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6ca488a3-c4d3-4dd1-916c-785c7d842fa1
⛔ Files ignored due to path filters (1)
example/pubspec.lockis excluded by!**/*.lock
📒 Files selected for processing (14)
.github/workflows/test.ymlREADME.mdexample/ios/Runner.xcodeproj/project.pbxprojexample/pubspec.yamlexample/test/widget_test.dartlib/form_validator.dartlib/validators/base32.dartlib/validators/ip.dartlib/validators/numeric.dartlib/validators/uuid.darttest/validators/base32_test.darttest/validators/date_test.darttest/validators/int_test.darttest/validators/uuid_test.dart
✅ Files skipped from review due to trivial changes (8)
- .github/workflows/test.yml
- lib/validators/base32.dart
- example/pubspec.yaml
- test/validators/int_test.dart
- test/validators/date_test.dart
- lib/validators/numeric.dart
- test/validators/uuid_test.dart
- example/ios/Runner.xcodeproj/project.pbxproj
🚧 Files skipped from review as they are similar to previous changes (3)
- test/validators/base32_test.dart
- lib/validators/uuid.dart
- lib/form_validator.dart
Summary
This PR introduces a major upgrade to flutter_validators, expanding the package with new validators, improved Flutter form integration, enhanced documentation, and full test coverage.
Key Changes
✨ New Validators
Added several commonly used validators to make the package more comprehensive:
🧩 Flutter Form Integration
Added a new
form_validator.dartutility that provides aValidatorclass designed for seamless use with Flutter'sTextFormField.validator.Each validator returns a
String? Function(String?)closure and supports custom error messages.Example:
🧪 Test Coverage
📦 Example App
Added a complete Flutter example project demonstrating how to use the validators in a real Flutter form.
📚 Documentation Improvements
⚙️ CI Setup
Added a GitHub Actions workflow to:
Miscellaneous
Summary by CodeRabbit
New Features
Documentation
Tests & CI
License