mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Extract url validator to its own file
This commit is contained in:
29
lib/core/validators.dart
Normal file
29
lib/core/validators.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
|
||||
String? validateUrl(String? value, AppLocalizations i18n, {bool required = true}) {
|
||||
// Required?
|
||||
if (required && (value == null || value.trim().isEmpty)) {
|
||||
return i18n.enterValue;
|
||||
}
|
||||
|
||||
if (!required && (value == null || value.trim().isEmpty)) {
|
||||
return null;
|
||||
}
|
||||
value = value!.trim();
|
||||
|
||||
if (!value.startsWith('http://') && !value.startsWith('https://')) {
|
||||
return i18n.invalidUrl;
|
||||
}
|
||||
|
||||
// Try to parse as URI
|
||||
try {
|
||||
final uri = Uri.parse(value);
|
||||
if (!uri.hasScheme || !uri.hasAuthority) {
|
||||
return i18n.invalidUrl;
|
||||
}
|
||||
} catch (e) {
|
||||
return i18n.invalidUrl;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
|
||||
/// The amount of characters an exercise description needs to have
|
||||
const MIN_CHARS_DESCRIPTION = 40;
|
||||
|
||||
/// The amount of characters an exercise name needs to have
|
||||
const MIN_CHARS_NAME = 5;
|
||||
const MAX_CHARS_NAME = 40;
|
||||
|
||||
String? validateName(String? name, BuildContext context) {
|
||||
if (name!.isEmpty) {
|
||||
return AppLocalizations.of(context).enterValue;
|
||||
}
|
||||
|
||||
if (name.length < MIN_CHARS_NAME || name.length > MAX_CHARS_NAME) {
|
||||
return AppLocalizations.of(context).enterCharacters(
|
||||
MIN_CHARS_NAME.toString(),
|
||||
MAX_CHARS_NAME.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String? validateExerciseDescription(String? name, BuildContext context) {
|
||||
if (name!.isEmpty) {
|
||||
return AppLocalizations.of(context).enterValue;
|
||||
}
|
||||
|
||||
if (name.length < MIN_CHARS_DESCRIPTION) {
|
||||
return AppLocalizations.of(context).enterMinCharacters(MIN_CHARS_DESCRIPTION.toString());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,36 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:wger/core/validators.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
|
||||
class ServerField extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final Function(String?) onSaved;
|
||||
|
||||
const ServerField({
|
||||
required this.controller,
|
||||
required this.onSaved,
|
||||
super.key,
|
||||
});
|
||||
const ServerField({required this.controller, required this.onSaved, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final i18n = AppLocalizations.of(context);
|
||||
|
||||
return TextFormField(
|
||||
key: const Key('inputServer'),
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).customServerUrl,
|
||||
helperText: AppLocalizations.of(context).customServerHint,
|
||||
labelText: i18n.customServerUrl,
|
||||
helperText: i18n.customServerHint,
|
||||
helperMaxLines: 4,
|
||||
),
|
||||
controller: controller,
|
||||
validator: (value) {
|
||||
if (Uri.tryParse(value!) == null) {
|
||||
return AppLocalizations.of(context).invalidUrl;
|
||||
}
|
||||
|
||||
if (value.isEmpty || !value.contains('http')) {
|
||||
return AppLocalizations.of(context).invalidUrl;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
validator: (value) => validateUrl(value, i18n, required: true),
|
||||
onSaved: onSaved,
|
||||
);
|
||||
}
|
||||
|
||||
49
test/core/validators_test.dart
Normal file
49
test/core/validators_test.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:wger/core/validators.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
|
||||
import 'validators_test.mocks.dart';
|
||||
|
||||
@GenerateMocks([AppLocalizations])
|
||||
void main() {
|
||||
MockAppLocalizations mockI18n = MockAppLocalizations();
|
||||
|
||||
setUp(() {
|
||||
mockI18n = MockAppLocalizations();
|
||||
|
||||
when(mockI18n.enterValue).thenReturn('Please enter a value');
|
||||
when(mockI18n.invalidUrl).thenReturn('Invalid URL');
|
||||
});
|
||||
|
||||
test('Required field empty returns error', () {
|
||||
final result = validateUrl('', mockI18n, required: true);
|
||||
expect(result, isNotNull);
|
||||
});
|
||||
|
||||
test('Optional field empty returns no error', () {
|
||||
final result = validateUrl('', mockI18n, required: false);
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('Invalid URL without http/https returns error', () {
|
||||
final result = validateUrl('www.example.com', mockI18n);
|
||||
expect(result, isNotNull);
|
||||
});
|
||||
|
||||
test('Invalid URL with wrong protocol returns error', () {
|
||||
final result = validateUrl('gopher://', mockI18n);
|
||||
expect(result, isNotNull);
|
||||
});
|
||||
|
||||
test('Valid http URL returns no error', () {
|
||||
final result = validateUrl('http://example.com', mockI18n);
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('Valid https URL returns no error', () {
|
||||
final result = validateUrl('https://example.com', mockI18n);
|
||||
expect(result, isNull);
|
||||
});
|
||||
}
|
||||
2799
test/core/validators_test.mocks.dart
Normal file
2799
test/core/validators_test.mocks.dart
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user