Skip to content

Commit

Permalink
Merge branch 'Issue109'
Browse files Browse the repository at this point in the history
  • Loading branch information
ChopinDavid committed Jul 28, 2023
2 parents 1d6ff0b + 953fd29 commit ba2bcc4
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 28 deletions.
41 changes: 41 additions & 0 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
PODS:
- "app_settings (3.0.0+1)":
- Flutter
- Flutter (1.0.0)
- image_picker_ios (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- video_player_avfoundation (0.0.1):
- Flutter

DEPENDENCIES:
- app_settings (from `.symlinks/plugins/app_settings/ios`)
- Flutter (from `Flutter`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)

EXTERNAL SOURCES:
app_settings:
:path: ".symlinks/plugins/app_settings/ios"
Flutter:
:path: Flutter
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/ios"

SPEC CHECKSUMS:
app_settings: d103828c9f5d515c4df9ee754dabd443f7cedcf3
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
video_player_avfoundation: 81e49bb3d9fb63dccf9fa0f6d877dc3ddbeac126

PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3

COCOAPODS: 1.12.1
57 changes: 37 additions & 20 deletions lib/ui/elements/multiple_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,50 @@ import 'package:flutter_survey_js/ui/reactive/reactive_nested_form.dart';
import 'package:flutter_survey_js/ui/survey_configuration.dart';
import 'package:flutter_survey_js_model/flutter_survey_js_model.dart' as s;
import 'package:reactive_forms/reactive_forms.dart';

import 'package:async/async.dart';

Widget multipleTextBuilder(BuildContext context, s.Elementbase element,
{ElementConfiguration? configuration}) {
final e = element as s.Multipletext;
final texts = (e.items?.toList() ?? []).map(toText).toList();

return ReactiveNestedForm(
formControlName: e.name,
child: ListView.separated(
physics: const ClampingScrollPhysics(),
shrinkWrap: true,
itemCount: texts.length,
itemBuilder: (BuildContext context, int index) {
final res = SurveyConfiguration.of(context)!
.factory
.resolve(context, texts[index]);
return index == 0
? Padding(
padding: const EdgeInsets.only(top: 8.0),
child: res,
)
: res;
},
separatorBuilder: (BuildContext context, int index) {
return SurveyConfiguration.of(context)!.separatorBuilder(context);
},
).wrapQuestionTitle(context, element, configuration: configuration));
child: Builder(builder: (context) {
final control = ReactiveForm.of(context) as FormGroup;
final effectiveDecoration = const InputDecoration()
.applyDefaults(Theme.of(context).inputDecorationTheme);
return StreamBuilder(
stream:
StreamGroup.merge([control.touchChanges, control.statusChanged]),
builder: (context, _) {
return InputDecorator(
decoration: effectiveDecoration.copyWith(
errorText: getErrorTextFromFormControl(context, control)),
child: ListView.separated(
physics: const ClampingScrollPhysics(),
shrinkWrap: true,
itemCount: texts.length,
itemBuilder: (BuildContext context, int index) {
final res = SurveyConfiguration.of(context)!
.factory
.resolve(context, texts[index]);
return index == 0
? Padding(
padding: const EdgeInsets.only(top: 8.0),
child: res,
)
: res;
},
separatorBuilder: (BuildContext context, int index) {
return SurveyConfiguration.of(context)!
.separatorBuilder(context);
},
),
);
},
);
}).wrapQuestionTitle(context, element, configuration: configuration));
}

AbstractControl multipleTextControlBuilder(
Expand Down
33 changes: 25 additions & 8 deletions lib/ui/validators.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,34 @@ class NonEmptyValidator extends Validator<dynamic> {
static ValidatorFunction get get => NonEmptyValidator().validate;
}

List<ValidatorFunction> questionToValidators(s.Question question) {
return surveyToValidators(
isRequired: question.isRequired,
validators: question.validators?.map((p) => p.realValidator).toList());
class MultipleTextNonEmptyValidator extends Validator<dynamic> {
@override
Map<String, dynamic>? validate(AbstractControl<dynamic> control) {
final error = <String, dynamic>{ValidationMessage.required: true};

bool hasError = true;

for (final value in control.value.values) {
if (value != null && value.toString().trim().isNotEmpty) {
hasError = false;
}
}

return hasError ? error : null;
}

static ValidatorFunction get get => MultipleTextNonEmptyValidator().validate;
}

List<ValidatorFunction> surveyToValidators(
{bool? isRequired, List<s.Surveyvalidator>? validators}) {
List<ValidatorFunction> questionToValidators(s.Question question) {
final res = <ValidatorFunction>[];
if (isRequired == true) {
res.add(NonEmptyValidator.get);
final validators = question.validators?.map((p) => p.realValidator).toList();
if (question.isRequired == true) {
if (question is s.Multipletext) {
res.add(MultipleTextNonEmptyValidator.get);
} else {
res.add(NonEmptyValidator.get);
}
}
if (validators != null) {
for (var value in validators) {
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ dependencies:
built_value: ">=8.4.0 <9.0.0"
built_collection: ">=5.1.1 <6.0.0"
intl: ^0.18.0
async: ^2.11.0

dev_dependencies:
flutter_test:
Expand Down
149 changes: 149 additions & 0 deletions test/questions/multiple_text_test.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_survey_js/survey.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:reactive_forms/reactive_forms.dart';

void main() {
// 单一的测试
Expand Down Expand Up @@ -72,4 +73,152 @@ void main() {
expect(find.text(placeholder1), findsOneWidget);
expect(find.text(placeholder2), findsOneWidget);
});

testWidgets('is not valid when required and no item has a value',
(WidgetTester tester) async {
final SurveyController surveyController = SurveyController();
bool onErrorsCalled = false;
final s = surveyFromJson(
{
"pages": [
{
"name": "page1",
"elements": [
{
"type": "multipletext",
"name": "question1",
"title": "Multiple text",
"isRequired": true,
"items": [
{"name": "text1"},
{"name": "text2"}
]
}
]
}
]
},
)!;
await tester.pumpWidget(
MaterialApp(
localizationsDelegates: const [
MultiAppLocalizationsDelegate(),
],
home: Material(
child: SurveyWidget(
survey: s,
controller: surveyController,
onErrors: (data) {
onErrorsCalled = true;
}),
),
),
);
await tester.pump();
await tester.idle();

surveyController.submit();
await tester.pumpAndSettle();
expect(onErrorsCalled, isTrue);
});

testWidgets('is valid when required and any item has a value',
(WidgetTester tester) async {
final SurveyController surveyController = SurveyController();
bool onErrorsCalled = false;
final s = surveyFromJson(
{
"pages": [
{
"name": "page1",
"elements": [
{
"type": "multipletext",
"name": "question1",
"title": "Multiple text",
"isRequired": true,
"items": [
{"name": "text1"},
{"name": "text2"}
]
}
]
}
]
},
)!;
await tester.pumpWidget(
MaterialApp(
localizationsDelegates: const [
MultiAppLocalizationsDelegate(),
],
home: Material(
child: SurveyWidget(
survey: s,
controller: surveyController,
onErrors: (data) {
onErrorsCalled = true;
}),
),
),
);
await tester.pump();
await tester.idle();

await tester.enterText(find.byType(ReactiveTextField).first, 'some answer');

surveyController.submit();
await tester.pumpAndSettle();
expect(onErrorsCalled, isFalse);
});

testWidgets('is not valid when required and all items are null or empty',
(WidgetTester tester) async {
final SurveyController surveyController = SurveyController();
bool onErrorsCalled = false;
final s = surveyFromJson(
{
"pages": [
{
"name": "page1",
"elements": [
{
"type": "multipletext",
"name": "question1",
"title": "Multiple text",
"isRequired": true,
"items": [
{"name": "text1"},
{"name": "text2"}
]
}
]
}
]
},
)!;
await tester.pumpWidget(
MaterialApp(
localizationsDelegates: const [
MultiAppLocalizationsDelegate(),
],
home: Material(
child: SurveyWidget(
survey: s,
controller: surveyController,
onErrors: (data) {
onErrorsCalled = true;
}),
),
),
);
await tester.pump();
await tester.idle();

await tester.enterText(find.byType(ReactiveTextField).first, ' ');

surveyController.submit();
await tester.pumpAndSettle();
expect(onErrorsCalled, isTrue);
});
}

0 comments on commit ba2bcc4

Please sign in to comment.