Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.
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
2 changes: 2 additions & 0 deletions lib/directive/module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ class NgDirectiveModule extends Module {
value(NgModelUrlValidator, null);
value(NgModelEmailValidator, null);
value(NgModelNumberValidator, null);
value(NgModelMaxNumberValidator, null);
value(NgModelMinNumberValidator, null);
value(NgModelPatternValidator, null);
value(NgModelMinLengthValidator, null);
value(NgModelMaxLengthValidator, null);
Expand Down
93 changes: 93 additions & 0 deletions lib/directive/ng_model_validators.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class NgModelEmailValidator implements NgValidatable {
* Validates the model to see if its contents match a valid number.
*/
@NgDirective(selector: 'input[type=number][ng-model]')
@NgDirective(selector: 'input[type=range][ng-model]')
class NgModelNumberValidator implements NgValidatable {
String get name => 'number';

Expand All @@ -101,6 +102,98 @@ class NgModelNumberValidator implements NgValidatable {
}
}

/**
* Validates the model to see if the numeric value than or equal to the max value.
*/
@NgDirective(selector: 'input[type=number][ng-model][max]')
@NgDirective(selector: 'input[type=range][ng-model][max]')
@NgDirective(
selector: 'input[type=number][ng-model][ng-max]',
map: const {'ng-max': '=>max'})
@NgDirective(
selector: 'input[type=range][ng-model][ng-max]',
map: const {'ng-max': '=>max'})
class NgModelMaxNumberValidator implements NgValidatable {

double _max;
String get name => 'max';

NgModelMaxNumberValidator(NgModel ngModel) {
ngModel.addValidator(this);
}

@NgAttr('max')
get max => _max;
set max(value) {
try {
num parsedValue = double.parse(value);
_max = parsedValue.isNaN ? _max : parsedValue;
} catch(e) {};
}

bool isValid(value) {
if (value == null || max == null) return true;

try {
num parsedValue = double.parse(value.toString());
if (!parsedValue.isNaN) {
return parsedValue <= max;
}
} catch(exception, stackTrace) {}

//this validator doesn't care if the type conversation fails or the value
//is not a number (NaN) because NgModelNumberValidator will handle the
//number-based validation either way.
return true;
}
}

/**
* Validates the model to see if the numeric value is greater than or equal to the min value.
*/
@NgDirective(selector: 'input[type=number][ng-model][min]')
@NgDirective(selector: 'input[type=range][ng-model][min]')
@NgDirective(
selector: 'input[type=number][ng-model][ng-min]',
map: const {'ng-min': '=>min'})
@NgDirective(
selector: 'input[type=range][ng-model][ng-min]',
map: const {'ng-min': '=>min'})
class NgModelMinNumberValidator implements NgValidatable {

double _min;
String get name => 'min';

NgModelMinNumberValidator(NgModel ngModel) {
ngModel.addValidator(this);
}

@NgAttr('min')
get min => _min;
set min(value) {
try {
num parsedValue = double.parse(value);
_min = parsedValue.isNaN ? _min : parsedValue;
} catch(e) {};
}

bool isValid(value) {
if (value == null || min == null) return true;

try {
num parsedValue = double.parse(value.toString());
if (!parsedValue.isNaN) {
return parsedValue >= min;
}
} catch(exception, stackTrace) {}

//this validator doesn't care if the type conversation fails or the value
//is not a number (NaN) because NgModelNumberValidator will handle the
//number-based validation either way.
return true;
}
}

/**
* Validates the model to see if its contents match the given pattern present on either the
* HTML pattern or ng-pattern attributes present on the input element.
Expand Down
180 changes: 176 additions & 4 deletions test/directive/ng_model_validators_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@ library ng_model_validators;
import '../_specs.dart';

void main() {
they(should, tokens, callback, [exclusive=false]) {
tokens.forEach((token) {
describe(token, () {
(exclusive ? iit : it)(should, () => callback(token));
});
});
}

tthey(should, tokens, callback) =>
they(should, tokens, callback, true);

describe('ngModel validators', () {
TestBed _;

Expand Down Expand Up @@ -128,9 +139,12 @@ void main() {
}));
});

describe('[type="number"]', () {
it('should validate the input field given a valid or invalid number', inject((RootScope scope) {
_.compile('<input type="number" ng-model="val" probe="i" />');
describe('[type="number|range"]', () {
they('should validate the input field given a valid or invalid number',
['range', 'number'],
(type) {

_.compile('<input type="$type" ng-model="val" probe="i" />');
Probe probe = _.rootScope.context['i'];
var model = probe.directive(NgModel);

Expand Down Expand Up @@ -162,7 +176,165 @@ void main() {
model.validate();
expect(model.valid).toEqual(false);
expect(model.invalid).toEqual(true);
}));
});

they('should perform a max number validation if a max attribute value is present',
['range', 'number'],
(type) {

_.compile('<input type="$type" ng-model="val" max="10" probe="i" />');
Probe probe = _.rootScope.context['i'];
var model = probe.directive(NgModel);

_.rootScope.apply(() {
_.rootScope.context['val'] = "8";
});

model.validate();
expect(model.valid).toEqual(true);
expect(model.invalid).toEqual(false);
expect(model.hasError('max')).toBe(false);

_.rootScope.apply(() {
_.rootScope.context['val'] = "99";
});

model.validate();
expect(model.valid).toEqual(false);
expect(model.invalid).toEqual(true);
expect(model.hasError('max')).toBe(true);

_.rootScope.apply(() {
_.rootScope.context['val'] = "a";
});

model.validate();
expect(model.valid).toEqual(false);
expect(model.invalid).toEqual(true);
expect(model.hasError('max')).toBe(false);
expect(model.hasError('number')).toBe(true);
});

they('should perform a max number validation if a ng-max attribute value is present and/or changed',
['range', 'number'],
(type) {

_.compile('<input type="$type" ng-model="val" ng-max="maxVal" probe="i" />');
Probe probe = _.rootScope.context['i'];
var model = probe.directive(NgModel);

//should be valid even when no number is present
model.validate();
expect(model.valid).toEqual(true);
expect(model.invalid).toEqual(false);
expect(model.hasError('max')).toBe(false);

_.rootScope.apply(() {
_.rootScope.context['val'] = "20";
});

model.validate();
expect(model.valid).toEqual(true);
expect(model.invalid).toEqual(false);
expect(model.hasError('max')).toBe(false);

_.rootScope.apply(() {
_.rootScope.context['maxVal'] = "19";
});

model.validate();
expect(model.valid).toEqual(false);
expect(model.invalid).toEqual(true);
expect(model.hasError('max')).toBe(true);

_.rootScope.apply(() {
_.rootScope.context['maxVal'] = "22";
});

model.validate();
expect(model.valid).toEqual(true);
expect(model.invalid).toEqual(false);
expect(model.hasError('max')).toBe(false);
});

they('should perform a min number validation if a min attribute value is present',
['range', 'number'],
(type) {

_.compile('<input type="$type" ng-model="val" min="-10" probe="i" />');
Probe probe = _.rootScope.context['i'];
var model = probe.directive(NgModel);

_.rootScope.apply(() {
_.rootScope.context['val'] = "8";
});

model.validate();
expect(model.valid).toEqual(true);
expect(model.invalid).toEqual(false);
expect(model.hasError('min')).toBe(false);

_.rootScope.apply(() {
_.rootScope.context['val'] = "-20";
});

model.validate();
expect(model.valid).toEqual(false);
expect(model.invalid).toEqual(true);
expect(model.hasError('min')).toBe(true);

_.rootScope.apply(() {
_.rootScope.context['val'] = "x";
});

model.validate();
expect(model.valid).toEqual(false);
expect(model.invalid).toEqual(true);
expect(model.hasError('min')).toBe(false);
expect(model.hasError('number')).toBe(true);
});

they('should perform a min number validation if a ng-min attribute value is present and/or changed',
['range', 'number'],
(type) {

_.compile('<input type="$type" ng-model="val" ng-min="minVal" probe="i" />');
Probe probe = _.rootScope.context['i'];
var model = probe.directive(NgModel);

//should be valid even when no number is present
model.validate();
expect(model.valid).toEqual(true);
expect(model.invalid).toEqual(false);
expect(model.hasError('min')).toBe(false);

_.rootScope.apply(() {
_.rootScope.context['val'] = "5";
});

model.validate();
expect(model.valid).toEqual(true);
expect(model.invalid).toEqual(false);
expect(model.hasError('min')).toBe(false);

_.rootScope.apply(() {
_.rootScope.context['minVal'] = "5.5";
});

model.validate();
expect(model.valid).toEqual(false);
expect(model.invalid).toEqual(true);
expect(model.hasError('min')).toBe(true);

_.rootScope.apply(() {
_.rootScope.context['val'] = "5.6";
});

model.validate();
expect(model.valid).toEqual(true);
expect(model.invalid).toEqual(false);
expect(model.hasError('min')).toBe(false);
});
});

describe('pattern', () {
Expand Down