Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 120 additions & 19 deletions lib/src/models/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ abstract class AbstractControl<T> {
bool _pristine = true;

T? _value;
final PureControlValues<T>? _pureValues;

ControlStatus _status;

Expand All @@ -52,9 +53,16 @@ abstract class AbstractControl<T> {
int asyncValidatorsDebounceTime = 250,
bool disabled = false,
bool touched = false,
PureControlValues<T>? pureValues,
}) : assert(asyncValidatorsDebounceTime >= 0),
_asyncValidatorsDebounceTime = asyncValidatorsDebounceTime,
_touched = touched,
_pureValues = pureValues ??
PureControlValues<T>(
asyncValidatorsDebounceTime: asyncValidatorsDebounceTime,
disabled: disabled,
touched: touched,
),
_status = disabled ? ControlStatus.disabled : ControlStatus.valid {
setValidators(validators);
setAsyncValidators(asyncValidators);
Expand Down Expand Up @@ -184,6 +192,12 @@ abstract class AbstractControl<T> {
/// The current value of the control.
T? get value => _value;

/// The fisrt value of the control.
T? get pureValue => _pureValues?.value;

/// The first values of the control.
PureControlValues<T>? get pureValues => _pureValues;

/// Sets the value to the control
set value(T? value) {
updateValue(value);
Expand Down Expand Up @@ -548,7 +562,13 @@ abstract class AbstractControl<T> {
markAsPristine(updateParent: updateParent);
markAsUntouched(updateParent: updateParent);

updateValue(value, updateParent: updateParent, emitEvent: emitEvent);
updateValue(
value ?? _pureValues?.value,
updateParent: updateParent,
emitEvent: emitEvent,
);

_touched = _pureValues?.touched ?? false;

if (disabled != null) {
disabled
Expand Down Expand Up @@ -833,12 +853,24 @@ class FormControl<T> extends AbstractControl<T> {
///
FormControl({
T? value,
super.validators,
super.asyncValidators,
super.asyncValidatorsDebounceTime,
super.touched,
super.disabled,
}) {
List<Validator<dynamic>> validators = const [],
List<AsyncValidator<dynamic>> asyncValidators = const [],
int asyncValidatorsDebounceTime = 250,
bool touched = false,
bool disabled = false,
}) : super(
pureValues: PureControlValues<T>(
asyncValidatorsDebounceTime: asyncValidatorsDebounceTime,
disabled: disabled,
touched: touched,
value: value,
),
validators: validators,
asyncValidators: asyncValidators,
asyncValidatorsDebounceTime: asyncValidatorsDebounceTime,
disabled: disabled,
touched: touched,
) {
if (value != null) {
this.value = value;
} else {
Expand Down Expand Up @@ -1027,11 +1059,22 @@ class FormControl<T> extends AbstractControl<T> {
/// that emits events each time you add or remove a control to the collection.
abstract class FormControlCollection<T> extends AbstractControl<T> {
FormControlCollection({
super.validators,
super.asyncValidators,
super.asyncValidatorsDebounceTime,
super.disabled,
});
List<Validator<dynamic>> validators = const [],
List<AsyncValidator<dynamic>> asyncValidators = const [],
int asyncValidatorsDebounceTime = 250,
bool disabled = false,
T? pureValue,
}) : super(
pureValues: PureControlValues<T>(
asyncValidatorsDebounceTime: asyncValidatorsDebounceTime,
disabled: disabled,
value: pureValue,
),
validators: validators,
asyncValidators: asyncValidators,
asyncValidatorsDebounceTime: asyncValidatorsDebounceTime,
disabled: disabled,
);

final _collectionChanges =
StreamController<List<AbstractControl<Object?>>>.broadcast();
Expand Down Expand Up @@ -1144,10 +1187,12 @@ class FormGroup extends FormControlCollection<Map<String, Object?>> {
super.asyncValidatorsDebounceTime,
bool disabled = false,
}) : assert(
!controls.keys.any((name) => name.contains(_controlNameDelimiter)),
'Control name should not contain dot($_controlNameDelimiter)',
),
super(disabled: disabled) {
!controls.keys.any((name) => name.contains(_controlNameDelimiter)),
'Control name should not contain dot($_controlNameDelimiter)'),
super(
disabled: disabled,
pureValue: controls,
) {
addAll(controls);

if (disabled) {
Expand Down Expand Up @@ -1511,8 +1556,8 @@ class FormGroup extends FormControlCollection<Map<String, Object?>> {
} else {
_controls.forEach((name, control) {
control.reset(
value: state[name]?.value,
disabled: state[name]?.disabled,
value: state[name]?.value ?? control.pureValues?.value,
disabled: state[name]?.disabled ?? control.pureValues?.disabled,
removeFocus: removeFocus,
updateParent: false,
);
Expand Down Expand Up @@ -1600,6 +1645,45 @@ class FormGroup extends FormControlCollection<Map<String, Object?>> {
@override
AbstractControl<dynamic>? findControl(String path) =>
findControlInCollection(path.split(_controlNameDelimiter));

@override
void reset({
Map<String, Object?>? value,
bool updateParent = true,
bool emitEvent = true,
bool removeFocus = false,
bool? disabled,
}) {
markAsPristine(updateParent: updateParent);
markAsUntouched(updateParent: updateParent);

if (value == null) {
for (var control in _controls.values) {
control.reset(
disabled: control.pureValues?.disabled,
updateParent: updateParent,
emitEvent: emitEvent,
removeFocus: removeFocus,
);
}
} else {
updateValue(
value,
updateParent: updateParent,
emitEvent: emitEvent,
);
}

if (disabled != null) {
disabled
? markAsDisabled(updateParent: true, emitEvent: false)
: markAsEnabled(updateParent: true, emitEvent: false);
}

if (removeFocus) {
unfocus(touched: false);
}
}
}

/// A FormArray aggregates the values of each child FormControl into an array.
Expand Down Expand Up @@ -1654,7 +1738,10 @@ class FormArray<T> extends FormControlCollection<List<T?>> {
super.asyncValidators,
super.asyncValidatorsDebounceTime,
bool disabled = false,
}) : super(disabled: disabled) {
}) : super(
disabled: disabled,
pureValue: controls.map((e) => e.value).toList(),
) {
addAll(controls);

if (disabled) {
Expand Down Expand Up @@ -2247,3 +2334,17 @@ class FormArray<T> extends FormControlCollection<List<T?>> {
AbstractControl<dynamic>? findControl(String path) =>
findControlInCollection(path.split(_controlNameDelimiter));
}

class PureControlValues<T> {
final int asyncValidatorsDebounceTime;
final bool disabled;
final bool touched;
final T? value;

PureControlValues({
this.asyncValidatorsDebounceTime = 250,
this.disabled = false,
this.touched = false,
this.value,
});
}
63 changes: 40 additions & 23 deletions test/src/models/form_array_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ void main() {
);
});

test('Reset array restores default value of all items to null', () {
test('Reset array restores default value in each control', () {
// Given: an array with items with default values
final array = FormArray<int>([
FormControl<int>(value: 1),
Expand All @@ -118,30 +118,29 @@ void main() {
array.reset();

//Then: items has initial default values
expect(array.control('0').value, null);
expect(array.control('1').value, null);
expect(array.control('2').value, null);
expect(array.control('0').value, equals(1));
expect(array.control('1').value, equals(2));
expect(array.control('2').value, equals(3));
});

test(
'Reset array restores default value of all items to null when calling resetState with empty array',
() {
// Given: an array with items with default values
final array = FormArray<int>([
FormControl<int>(value: 1),
FormControl<int>(value: 2),
FormControl<int>(value: 3),
]);

// And: reset array
array.resetState([]);

//Then: items has initial default values
expect(array.control('0').value, null);
expect(array.control('1').value, null);
expect(array.control('2').value, null);
},
);
'Reset array restores default value of all items to intial values when calling resetState with empty array',
() {
// Given: an array with items with default values
final array = FormArray<int>([
FormControl<int>(value: 1),
FormControl<int>(value: 2),
FormControl<int>(value: 3),
]);

// And: reset array
array.resetState([]);

//Then: items has initial default values
expect(array.control('0').value, equals(1));
expect(array.control('1').value, equals(2));
expect(array.control('2').value, equals(3));
});

test('Reset array with initial values', () {
// Given: an array with items with default values
Expand Down Expand Up @@ -204,7 +203,7 @@ void main() {
array.resetState([ControlState(disabled: true)]);

//Then: items has initial reset values and are disabled
expect(array.control('0').value, null);
expect(array.control('0').value, equals(1));
expect(array.control('0').disabled, true);
});

Expand Down Expand Up @@ -314,6 +313,24 @@ void main() {
expect(array.value!.join(''), 'Reactive');
});

test('Reset FromArry after remove a control', () {
// Given: an array with two controls
final array = FormArray<String>([
FormControl<String>(value: 'Reactive'),
FormControl<String>(value: 'Forms'),
]);

// When: removed last control
array.remove(array.controls.last);

expect(array.controls.length, equals(1));

array.reset();

expect(array.controls.length, equals(2));
expect(array.controls.map((e) => e.value), equals(['Reactive', 'Forms']));
});

test('Insert control at index position', () {
// Given: an array with two controls
final array = FormArray<String>([
Expand Down
48 changes: 42 additions & 6 deletions test/src/models/form_control_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,18 +130,54 @@ void main() {
expect(control.status, ControlStatus.disabled);
});

test('Resets a control and sets initial value', () {
test('Resets a control with the pure value', () {
const pureValue = 'pureValue';

// Given: a touched control with some default value
final control = FormControl<String>(
value: pureValue,
touched: true,
);

// When: reset control with the pure value
control.reset();

// Expect: the control has initial values
expect(control.value, pureValue);
expect(control.touched, isTrue);
});

test('Resets a control with the pure value after change the value', () {
const pureValue = 'pureValue';

// Given: a touched control with some default value
final control = FormControl<String>(
value: pureValue,
touched: true,
);

// Change the pure value
control.updateValue('new value');

// When: reset control with the pure value
control.reset();

// Expect: the control has initial values
expect(control.value, pureValue);
expect(control.touched, isTrue);
});

test('Resets a control with custom value', () {
// Given: a touched control with some default value
final control = FormControl<String>(value: 'someValue', touched: true);

// When: reset control with other initial value
final initialValue = 'otherValue';
control.reset(value: initialValue);

// Expect: the control has initial value
// Expect: the control has initial values
expect(control.value, initialValue);
// And: control is untouched
expect(control.touched, false);
expect(control.touched, isTrue);
});

test('Resets a control and sets initial value and disabled state', () {
Expand All @@ -154,8 +190,8 @@ void main() {

// Then: the control has initial value
expect(control.value, initialValue);
// And: control is untouched
expect(control.touched, false);
expect(control.touched, isTrue);

// And: is disabled
expect(control.disabled, true);
});
Expand Down
Loading
Loading