diff --git a/.jscs.json b/.jscs.json
index 5d5a43647..ca5b5f7db 100644
--- a/.jscs.json
+++ b/.jscs.json
@@ -1,4 +1,5 @@
{
"preset": "google",
- "maximumLineLength": 100
+ "maximumLineLength": 100,
+ "disallowMultipleLineBreaks": false
}
diff --git a/docs/index.md b/docs/index.md
index a682f1507..d8bd41873 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -260,6 +260,7 @@ The context variables available to you are:
| error | The error code |
| title | Title of the field |
| value | The model value |
+| viewValue | The view value (probably the one you want) |
| form | form definition object for this field |
| schema | schema for this field |
diff --git a/examples/bootstrap-example.html b/examples/bootstrap-example.html
index e5b2991b3..83eb0784a 100644
--- a/examples/bootstrap-example.html
+++ b/examples/bootstrap-example.html
@@ -273,11 +273,12 @@
Schema
}).error(function() {
$scope.loadedData = 'dummy';
$scope.error = 'Failed to load gist.';
+ $scope.selectedTest = $scope.tests[0];
});
+ } else {
+ $scope.selectedTest = $scope.tests[0];
}
- $scope.selectedTest = $scope.tests[0];
-
$scope.$watch('selectedTest',function(val){
if (val) {
$http.get(val.data).then(function(res) {setNewData(res.data);});
diff --git a/gulp/tasks/protractor.js b/gulp/tasks/protractor.js
new file mode 100644
index 000000000..9a3bb1864
--- /dev/null
+++ b/gulp/tasks/protractor.js
@@ -0,0 +1,38 @@
+var gulp = require('gulp');
+
+// The protractor task
+var protractor = require('gulp-protractor');
+
+// Start a standalone server
+var webdriver_standalone = protractor.webdriver_standalone;
+
+// Download and update the selenium driver
+var webdriver_update = protractor.webdriver_update;
+
+// Downloads the selenium webdriver
+gulp.task('webdriver-update', webdriver_update);
+
+// Start the standalone selenium server
+// NOTE: This is not needed if you reference the
+// seleniumServerJar in your protractor.conf.js
+gulp.task('webdriver-standalone', webdriver_standalone);
+
+
+// Setting up the test task
+gulp.task('protractor', ['webdriver-update'], function(cb) {
+ gulp.src(['test/protractor/specs/**/*.js']).pipe(protractor.protractor({
+ configFile: 'test/protractor/conf.js',
+ })).on('error', function(e) {
+ console.log(e);
+ }).on('end', cb);
+});
+
+['validation-messages', 'custom-validation'].forEach(function(name) {
+ gulp.task('protractor:' + name, ['webdriver-update'], function(cb) {
+ gulp.src(['test/protractor/specs/' + name + '.js']).pipe(protractor.protractor({
+ configFile: 'test/protractor/conf.js',
+ })).on('error', function(e) {
+ console.log(e);
+ }).on('end', cb);
+ });
+});
diff --git a/package.json b/package.json
index d8c92e6d5..d4df929fb 100644
--- a/package.json
+++ b/package.json
@@ -38,6 +38,7 @@
"gulp-concat": "^2.2.0",
"gulp-jscs": "^1.1.0",
"gulp-minify-html": "^0.1.1",
+ "gulp-protractor": "^1.0.0",
"gulp-rename": "^1.2.0",
"gulp-uglify": "^0.2.1",
"karma": "^0.12.0",
@@ -49,6 +50,7 @@
"karma-phantomjs-launcher": "^0.1.4",
"mocha": "^1.18.0",
"mocha-lcov-reporter": "0.0.1",
+ "protractor": "^2.0.0",
"sinon": "^1.9.0",
"sinon-chai": "^2.5.0",
"streamqueue": "0.0.5"
diff --git a/src/directives/message.js b/src/directives/message.js
index 572bf36bf..b14518187 100644
--- a/src/directives/message.js
+++ b/src/directives/message.js
@@ -27,24 +27,13 @@ angular.module('schemaForm').directive('sfMessage',
// We only show one error.
// TODO: Make that optional
- // tv4- errors take precedence
var error = errors[0];
- if (errors.length > 1) {
-
- error = errors.reduce(function(prev, value) {
- if (prev && prev.indexOf('tv4-') === 0) {
- return prev;
- }
- return value;
- });
- console.log('reduced',errors, error)
-
- }
if (error) {
element.html(sfErrorMessage.interpolate(
error,
scope.ngModel.$modelValue,
+ scope.ngModel.$viewValue,
scope.form,
scope.options && scope.options.validationMessage
));
diff --git a/src/services/decorators.js b/src/services/decorators.js
index bd012b12f..7ca55a882 100644
--- a/src/services/decorators.js
+++ b/src/services/decorators.js
@@ -159,6 +159,7 @@ angular.module('schemaForm').provider('schemaFormDecorators',
return sfErrorMessage.interpolate(
(schemaError && schemaError.code + '') || 'default',
(scope.ngModel && scope.ngModel.$modelValue) || '',
+ (scope.ngModel && scope.ngModel.$viewValue) || '',
scope.form,
scope.options && scope.options.validationMessage
);
diff --git a/src/services/errors.js b/src/services/errors.js
index d00070e96..14038e09f 100644
--- a/src/services/errors.js
+++ b/src/services/errors.js
@@ -14,25 +14,25 @@ angular.module('schemaForm').provider('sfErrorMessage', function() {
12: 'Data is valid against more than one schema from "oneOf"',
13: 'Data matches schema from "not"',
// Numeric errors
- 100: 'Value {{value}} is not a multiple of {{schema.multipleOf}}',
- 101: 'Value {{value}} is less than minimum {{schema.minimum}}',
- 102: 'Value {{value}} is equal to exclusive minimum {{schema.minimum}}',
- 103: 'Value {{value}} is greater than maximum {{schema.maximum}}',
- 104: 'Value {{value}} is equal to exclusive maximum {{schema.maximum}}',
- 105: 'Value {{value}} is not a valid number',
+ 100: 'Value is not a multiple of {{schema.divisibleBy}}',
+ 101: '{{viewValue}} is less than the allowed minimum of {{schema.minimum}}',
+ 102: '{{viewValue}} is equal to the exclusive minimum {{schema.minimum}}',
+ 103: '{{viewValue}} is greater than the allowed maximum of {{schema.maximum}}',
+ 104: '{{viewValue}} is equal to the exclusive maximum {{schema.maximum}}',
+ 105: 'Value is not a valid number',
// String errors
- 200: 'String is too short ({{value.length}} chars), minimum {{schema.minimum}}',
- 201: 'String is too long ({{value.length}} chars), maximum {{schema.maximum}}',
+ 200: 'String is too short ({{viewValue.length}} chars), minimum {{schema.minLength}}',
+ 201: 'String is too long ({{viewValue.length}} chars), maximum {{schema.maxLength}}',
202: 'String does not match pattern: {{schema.pattern}}',
// Object errors
- 300: 'Too few properties defined, minimum {{schema.minimum}}',
- 301: 'Too many properties defined, maximum {{schema.maximum}}',
+ 300: 'Too few properties defined, minimum {{schema.minProperties}}',
+ 301: 'Too many properties defined, maximum {{schema.maxProperties}}',
302: 'Required',
303: 'Additional properties not allowed',
304: 'Dependency failed - key must exist',
// Array errors
- 400: 'Array is too short ({{value.length}}), minimum {{schema.minimum}}',
- 401: 'Array is too long ({{value.length}}), maximum {{schema.maximum}}',
+ 400: 'Array is too short ({{value.length}}), minimum {{schema.maxItems}}',
+ 401: 'Array is too long ({{value.length}}), maximum {{schema.minItems}}',
402: 'Array items are not unique',
403: 'Additional items not allowed',
// Format errors
@@ -44,6 +44,15 @@ angular.module('schemaForm').provider('sfErrorMessage', function() {
1000: 'Unknown property (not in schema)'
};
+ // In some cases we get hit with an angular validation error
+ defaultMessages.number = defaultMessages[105];
+ defaultMessages.required = defaultMessages[302];
+ defaultMessages.min = defaultMessages[101];
+ defaultMessages.max = defaultMessages[103];
+ defaultMessages.maxlength = defaultMessages[201];
+ defaultMessages.minlength = defaultMessages[200];
+ defaultMessages.pattern = defaultMessages[202];
+
this.setDefaultMessages = function(messages) {
defaultMessages = messages;
};
@@ -68,12 +77,14 @@ angular.module('schemaForm').provider('sfErrorMessage', function() {
* @param {string} error the error code, i.e. tv4-xxx for tv4 errors, otherwise it's whats on
* ngModel.$error for custom errors.
* @param {Any} value the actual model value.
+ * @param {Any} viewValue the viewValue
* @param {Object} form a form definition object for this field
* @param {Object} global the global validation messages object (even though its called global
* its actually just shared in one instance of sf-schema)
* @return {string} The error message.
*/
- service.interpolate = function(error, value, form, global) {
+ service.interpolate = function(error, value, viewValue, form, global) {
+ console.log(error, value, viewValue)
global = global || {};
var validationMessage = form.validationMessage || {};
@@ -99,6 +110,7 @@ angular.module('schemaForm').provider('sfErrorMessage', function() {
var context = {
error: error,
value: value,
+ viewValue: viewValue,
form: form,
schema: form.schema,
title: form.title || (form.schema && form.schema.title)
diff --git a/test/protractor/conf.js b/test/protractor/conf.js
index ff83a3634..8f51a93ee 100644
--- a/test/protractor/conf.js
+++ b/test/protractor/conf.js
@@ -1,4 +1,3 @@
exports.config = {
- seleniumAddress: 'http://localhost:4444/wd/hub',
- specs: ['custom-validation.js']
+ seleniumAddress: 'http://localhost:4444/wd/hub'
}
diff --git a/test/protractor/custom-validation.js b/test/protractor/specs/custom-validation.js
similarity index 100%
rename from test/protractor/custom-validation.js
rename to test/protractor/specs/custom-validation.js
diff --git a/test/protractor/specs/validation-messages.js b/test/protractor/specs/validation-messages.js
new file mode 100644
index 000000000..323275649
--- /dev/null
+++ b/test/protractor/specs/validation-messages.js
@@ -0,0 +1,66 @@
+/* global browser, it, describe, element, by */
+
+describe('Schema Form validation messages', function() {
+
+ describe('#string', function() {
+ var URL = 'http://localhost:8080/examples/bootstrap-example.html#/86fb7505a8ab6a43bc70';
+
+ it('should not complain if it gets a normal string', function() {
+ browser.get(URL);
+ var input = element.all(by.css('form[name=ngform] input')).first();
+ input.sendKeys('string');
+
+ expect(input.getAttribute('value')).toEqual('string');
+ expect(input.evaluate('ngModel.$valid')).toEqual(true);
+
+ });
+
+
+ var validationMessageTestBuider = function(nr, value, validationMessage) {
+ it('should say "' + validationMessage + '" when input is ' + value, function() {
+ browser.get(URL);
+ var input = element.all(by.css('form[name=ngform] input')).get(nr);
+ input.sendKeys(value);
+
+ var message = element.all(by.css('form[name=ngform] div[sf-message]')).get(nr);
+ expect(input.evaluate('ngModel.$valid')).toEqual(false);
+ expect(message.getText()).toEqual(validationMessage);
+
+ });
+ };
+
+ var stringTests = {
+ 's': 'String is too short (1 chars), minimum 3',
+ 'tooo long string': 'String is too long (11 chars), maximum 10',
+ 'foo 66': 'String does not match pattern: ^[a-zA-Z ]+$'
+ };
+
+ Object.keys(stringTests).forEach(function(value) {
+ validationMessageTestBuider(0, value, stringTests[value]);
+ });
+
+
+ var integerTests = {
+ '3': '3 is less than the allowed minimum of 6',
+ '66': '66 is greater than the allowed maximum of 50',
+ '11': 'Value is not a multiple of 3',
+ 'aaa': 'Value is not a valid number'
+ };
+
+ Object.keys(integerTests).forEach(function(value) {
+ validationMessageTestBuider(1, value, integerTests[value]);
+ });
+
+
+ it('should say "Required" when fields are required', function() {
+ browser.get(URL);
+ element.all(by.css('form[name=ngform]')).submit();
+ var input = element.all(by.css('form[name=ngform] input')).get(1);
+
+ var message = element.all(by.css('form[name=ngform] div[sf-message]')).get(1);
+ expect(input.evaluate('ngModel.$valid')).toEqual(false);
+ expect(message.getText()).toEqual('Required');
+
+ });
+ });
+});
diff --git a/test/services/messages-test.js b/test/services/messages-test.js
index a1a5202e6..57a91a97a 100644
--- a/test/services/messages-test.js
+++ b/test/services/messages-test.js
@@ -10,6 +10,7 @@ describe('schemaFormServices', function() {
var result = sfErrorMessage.interpolate(
'foobar-error', //error
'foobar', //value
+ 'foobar', //view value
{schema: {title: 'Foo'}}, //form
{'default': 'Oh noes!'}
);
@@ -25,6 +26,7 @@ describe('schemaFormServices', function() {
var result = sfErrorMessage.interpolate(
'foobar-error', //error
'foobar', //value
+ 'foobar', //view value
{validationMessage: {'default': 'Oh yes!'}, schema: {title: 'Foo'}}, //form
{'default': 'Oh noes!'}
);
@@ -40,6 +42,7 @@ describe('schemaFormServices', function() {
var result = sfErrorMessage.interpolate(
'foobar-error', //error
'foobar', //value
+ 'foobar', //view value
{schema: {title: 'Foo'}}, //form
{'default': 'Oh noes!', 'foobar-error': 'Aw chucks!'}
);
@@ -54,6 +57,7 @@ describe('schemaFormServices', function() {
var result = sfErrorMessage.interpolate(
'foobar-error', //error
'foobar', //value
+ 'foobar', //view value
{schema: {title: 'Foo'}, validationMessage: {'foobar-error': 'Noooooo!'}}, //form
{'default': 'Oh noes!', 'foobar-error': 'Aw chucks!'}
);
@@ -68,6 +72,7 @@ describe('schemaFormServices', function() {
var result = sfErrorMessage.interpolate(
'foobar-error', //error
'foobar', //value
+ 'foobar', //view value
{
schema: {title: 'Foo'},
validationMessage: {
@@ -87,6 +92,7 @@ describe('schemaFormServices', function() {
var result = sfErrorMessage.interpolate(
'foobar-error', //error
'foobar', //value
+ 'foobar', //view value
{
schema: {title: 'Foo'},
validationMessage: {
@@ -101,6 +107,7 @@ describe('schemaFormServices', function() {
result = sfErrorMessage.interpolate(
'foobar-error', //error
'foobar', //value
+ 'foobar', //view value
{
title: 'Bar',
schema: {title: 'Foo'},
@@ -121,6 +128,7 @@ describe('schemaFormServices', function() {
var result = sfErrorMessage.interpolate(
'foobar-error', //error
'foobar', //value
+ 'foobar', //view value
{
schema: {title: 'Foo'},
validationMessage: 'Huh?'
@@ -140,6 +148,7 @@ describe('schemaFormServices', function() {
var result = sfErrorMessage.interpolate(
'foobar-error', //error
'foobar', //value
+ 'foobar', //view value
{
schema: {title: 'Foo'},
validationMessage: {
@@ -174,6 +183,7 @@ describe('schemaFormServices', function() {
var result = sfErrorMessage.interpolate(
'foobar-error', //error
'foobar', //value
+ 'foobar', //view value
{
schema: {title: 'Foo'},
validationMessage: msgFn
@@ -201,7 +211,8 @@ describe('schemaFormServices', function() {
var result = sfErrorMessage.interpolate(
'tv4-302', //error
- 'foobar', //value
+ 'foobar', //value
+ 'foobar', //view value
{
schema: {title: 'Foo'},
validationMessage: {302: 'tv4 error!'}