Extract url validator to its own file

This commit is contained in:
Roland Geider
2025-10-08 14:03:25 +02:00
parent 6b6afbb528
commit 0add2a6bd1
5 changed files with 2884 additions and 53 deletions

29
lib/core/validators.dart Normal file
View 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;
}

View File

@@ -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;
}

View File

@@ -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,
);
}

View 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);
});
}

File diff suppressed because it is too large Load Diff