Getting close, adds tests, fixese the key being set on the block, gets errors clearing, now need to make the UI behave.

This commit is contained in:
Shannon
2020-07-10 18:06:11 +10:00
parent 00c83182e7
commit bbbb7a052b
9 changed files with 222 additions and 246 deletions

View File

@@ -19,7 +19,7 @@
},
bindings: {
property: "=",
elementUdi: "@",
elementKey: "@",
// optional, if set this will be used for the property alias validation path (hack required because NC changes the actual property.alias :/)
propertyAlias: "@",
showInherit: "<",
@@ -44,20 +44,14 @@
vm.propertyActions = actions;
};
// returns the unique Id for the property to be used as the validation key for server side validation logic
// returns the validation path for the property to be used as the validation key for server side validation logic
vm.getValidationPath = function () {
// the elementUdi will be empty when this is not a nested property
var parentValidationPath = vm.parentUmbProperty ? vm.parentUmbProperty.getValidationPath() : null;
var propAlias = vm.propertyAlias ? vm.propertyAlias : vm.property.alias;
vm.elementUdi = ensureUdi(vm.elementUdi);
return serverValidationManager.createPropertyValidationKey(propAlias, vm.elementUdi);
}
vm.getParentValidationPath = function () {
if (!vm.parentUmbProperty) {
return null;
}
return vm.parentUmbProperty.getValidationPath();
// the elementKey will be empty when this is not a nested property
var valPath = vm.elementKey ? vm.elementKey + "/" + propAlias : propAlias;
return serverValidationManager.createPropertyValidationKey(valPath, parentValidationPath);
}
function onInit() {
@@ -70,8 +64,6 @@
});
}
vm.elementUdi = ensureUdi(vm.elementUdi);
if (!vm.parentUmbProperty) {
// not found, then fallback to searching the scope chain, this may be needed when DOM inheritance isn't maintained but scope
// inheritance is (i.e.infinite editing)
@@ -80,13 +72,6 @@
}
}
// if only a guid is passed in, we'll ensure a correct udi structure
function ensureUdi(udi) {
if (udi && !udi.startsWith("umb://")) {
udi = udiService.build("element", udi);
}
return udi;
}
}
})();

View File

@@ -24,6 +24,7 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel
var unsubscribe = [];
var watcher = null;
var hasError = false;
var hasServerError = false; // tracks if this validator has an explicit server validator key attached to it
//create properties on our custom scope so we can use it in our template
scope.errorMsg = "";
@@ -49,9 +50,6 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel
var isMandatory = currentProperty.validation ? currentProperty.validation.mandatory : undefined;
var labels = {};
localizationService.localize("errors_propertyHasErrors").then(function (data) {
labels.propertyHasErrors = data;
});
if (umbVariantCtrl) {
//if we are inside of an umbVariantContent directive
@@ -69,7 +67,6 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel
// if we have reached this part, and there is no culture, then lets fallback to invariant. To get the validation feedback for invariant language.
currentCulture = currentCulture || "invariant";
// Gets the error message to display
function getErrorMsg() {
//this can be null if no property was assigned
@@ -96,11 +93,6 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel
// the form. Of course normal client-side validators will continue to execute.
function startWatch() {
// TODO: Can we watch on something other than the value?? This doesn't work for complex editors especially once that have a
// viewmodel/model setup the value that this is watching doesn't actually get updated by a sub-editor in all cases.
// we can probably watch the formCtrl view value? But then we also don't want this to watch complex values that have sub editors anyways
// since that might end up clearing the whole chain of valPropertyMsg when a sub value is changed (in some cases, not with the block editor).
//if there's not already a watch
if (!watcher) {
@@ -125,6 +117,25 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel
if (errCount === 0
|| (errCount === 1 && Utilities.isArray(formCtrl.$error.valPropertyMsg))
|| (formCtrl.$invalid && Utilities.isArray(formCtrl.$error.valServer))) {
console.log("RESETTING ERROR FROM WATCH " + propertyValidationKey + " - " + hasServerError);
if (hasServerError) {
// check if we can clear it based on child server errors, if we are the only explicit one remaining we can clear ourselves
var nestedErrs = serverValidationManager.getPropertyErrorsByValidationPath(
propertyValidationKey,
currentCulture,
"",
currentSegment,
true);
if (nestedErrs.length === 1 && nestedErrs[0].propertyAlias === propertyValidationKey) {
console.log("CLEARING SERVER VAL FROM WATCH " + propertyValidationKey);
serverValidationManager.removePropertyError(propertyValidationKey, currentCulture, "", currentSegment);
}
}
resetError();
}
else if (showValidation && scope.errorMsg === "") {
@@ -144,104 +155,18 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel
}
function resetError() {
var hadError = hasError;
hasError = false;
formCtrl.$setValidity('valPropertyMsg', true, formCtrl);
scope.errorMsg = "";
stopWatch();
// if we had an error, then check on the current valFormManager to see if it's
// now valid, if it is it means that the containing form (i.e. the form rendering)
// properties for an element/content type) is now valid which means we can clear
// the parent's valPropertyMsg if there is one. This will only occur with complex editors
// where we have nested umb-property components.
if (hadError) {
scope.$evalAsync(function () {
//// TODO: This does not work :( :( :(
//// We cannot clear a val-property-msg because another nested child might have server validation errors too.
//// I 'think' we might be able to set the UI validation of this val-property-msg based on the child validators as well
//// as the server validator so it can 'just' unset itself if all child validators are cleared. Can it be done?
//// Here we loop over the umbProperty hierarchy to see if we should clear the val-property-msg server validation key.
//// we will clear the key if the parent for is valid, or if the parent form is only invalid due to a single val-property-msg error.
//var currUmbProperty = umbPropCtrl;
//var parentValidationKey = currUmbProperty.getParentValidationPath();
//while (currUmbProperty && parentValidationKey) {
// if (!currUmbProperty.parentForm.$invalid || (_.keys(currUmbProperty.parentForm.$error).length === 1 && currUmbProperty.parentForm.$error.valPropertyMsg)) {
// serverValidationManager.removePropertyError(parentValidationKey, currentCulture, "", currentSegment);
// // re-assign and loop
// if (currUmbProperty !== umbPropCtrl.parentUmbProperty) {
// currUmbProperty = umbPropCtrl.parentUmbProperty;
// parentValidationKey = currUmbProperty ? currUmbProperty.getParentValidationPath() : null;
// }
// else {
// break;
// }
// }
// else {
// break;
// }
//}
//// we need to navigate the parentForm here, unfortunately there's no real alternative unless we create our own directive
//// of some sort but that would also get messy. This works though since in this case we're always going to be in the property
//// form and the parent form about this will contain any invalid flags for all the other sibling properties. So when that is
//// no longer invalid, we can check if we have a parent validation key (meaning we'd be nested inside of umb-property) and
//// we can clear that server error.
//// TODO: If there is another server error for this property though this might clear it inadvertently, at this time I'm unsure how to deal with that.
//var parentValidationKey = umbPropCtrl.getParentValidationPath();
//if (parentValidationKey) {
// // TODO: Instead of using the parent form, can we 'just' use umbProperty again which itself can check if it's
// // parent form is valid? then below we can call in a loop each parent umb property check if it has a parent validation
// // path and check if it's form is valid, this will recursively perform this logic up the chain.
// var parentForm = formCtrl.$$parentForm;
// if (parentForm && !parentForm.$invalid) {
// // TODO: Though this works for one level, if you have errors at level 1 and 2, clear errors at level when
// // and then level 2, then only the val-property-msg is cleared at level 1 and not also at level 0.
// // So we still need to recurse up the chain to deal with this
// serverValidationManager.removePropertyError(parentValidationKey, currentCulture, "", currentSegment);
// }
//}
});
}
}
//function checkFormValidation(f) {
// if (!angularHelper.isForm(f)) {
// throw "The object is not an angular Form";
// }
// // if there's no value, the controls can be reset, which clears the error state on formCtrl
// for (let control of formCtrl.$getControls()) {
// control.$setValidity();
// }
//}
function checkValidationStatus() {
if (formCtrl.$invalid) {
//first we need to check if the valPropertyMsg validity is invalid
if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) {
//since we already have an error we'll just return since this means we've already set the
// hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe
// TODO: This does not work! we cannot just clear if there are no errors in child controls because child controls
// won't even have been loaded yet so this will just instantly clear them
//// At this stage we might have an error assigned because it was assigned based on validation hierarchy from the server,
//// BUT one or ALL of the child server (and client) validations may be cleared at this point. We will know if we have an
//// explicitly assigned error based on the error message assigned, if it is a non-explicit error (meaning that the error
//// was assigned because it has child errors) then the message will just be: labels.propertyHasErrors
//if (scope.errorMsg === labels.propertyHasErrors && _.every(formCtrl.$getControls(), c => c.$valid)) {
// resetError();
//}
//hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe
return;
}
//if there are any errors in the current property form that are not valPropertyMsg
@@ -275,73 +200,92 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel
}
}
//if there's any remaining errors in the server validation service then we should show them.
var showValidation = serverValidationManager.items.length > 0;
if (!showValidation) {
//We can either get the form submitted status by the parent directive valFormManager (if we add a property to it)
//or we can just check upwards in the DOM for the css class (easier for now).
//The initial hidden state can't always be hidden because when we switch variants in the content editor we cannot
//reset the status.
showValidation = element.closest(".show-validation").length > 0;
}
function onInit() {
localizationService.localize("errors_propertyHasErrors").then(function (data) {
//listen for form validation changes.
//The alternative is to add a watch to formCtrl.$invalid but that would lead to many more watches then
// subscribing to this single watch.
valFormManager.onValidationStatusChanged(function (evt, args) {
checkValidationStatus();
});
labels.propertyHasErrors = data;
//listen for the forms saving event
unsubscribe.push(scope.$on("formSubmitting", function (ev, args) {
showValidation = true;
if (hasError && scope.errorMsg === "") {
scope.errorMsg = getErrorMsg();
startWatch();
}
else if (!hasError) {
resetError();
}
}));
//listen for the forms saved event
unsubscribe.push(scope.$on("formSubmitted", function (ev, args) {
showValidation = false;
resetError();
}));
//listen for server validation changes
// NOTE: we pass in "" in order to listen for all validation changes to the content property, not for
// validation changes to fields in the property this is because some server side validators may not
// return the field name for which the error belongs too, just the property for which it belongs.
// It's important to note that we need to subscribe to server validation changes here because we always must
// indicate that a content property is invalid at the property level since developers may not actually implement
// the correct field validation in their property editors.
if (scope.currentProperty) { //this can be null if no property was assigned
function serverValidationManagerCallback(isValid, propertyErrors, allErrors) {
hasError = !isValid;
if (hasError) {
//set the error message to the server message
scope.errorMsg = propertyErrors[0].errorMsg ? propertyErrors[0].errorMsg : labels.propertyHasErrors;
//flag that the current validator is invalid
formCtrl.$setValidity('valPropertyMsg', false, formCtrl);
startWatch();
//if there's any remaining errors in the server validation service then we should show them.
var showValidation = serverValidationManager.items.length > 0;
if (!showValidation) {
//We can either get the form submitted status by the parent directive valFormManager (if we add a property to it)
//or we can just check upwards in the DOM for the css class (easier for now).
//The initial hidden state can't always be hidden because when we switch variants in the content editor we cannot
//reset the status.
showValidation = element.closest(".show-validation").length > 0;
}
else {
//listen for form validation changes.
//The alternative is to add a watch to formCtrl.$invalid but that would lead to many more watches then
// subscribing to this single watch.
valFormManager.onValidationStatusChanged(function (evt, args) {
checkValidationStatus();
});
//listen for the forms saving event
unsubscribe.push(scope.$on("formSubmitting", function (ev, args) {
showValidation = true;
if (hasError && scope.errorMsg === "") {
scope.errorMsg = getErrorMsg();
startWatch();
}
else if (!hasError) {
resetError();
}
}));
//listen for the forms saved event
unsubscribe.push(scope.$on("formSubmitted", function (ev, args) {
showValidation = false;
resetError();
}
}
}));
unsubscribe.push(serverValidationManager.subscribe(
propertyValidationKey,
currentCulture,
"",
serverValidationManagerCallback,
currentSegment
));
if (scope.currentProperty) { //this can be null if no property was assigned
//listen for server validation changes
// NOTE: we pass in "" in order to listen for all validation changes to the content property, not for
// validation changes to fields in the property this is because some server side validators may not
// return the field name for which the error belongs too, just the property for which it belongs.
// It's important to note that we need to subscribe to server validation changes here because we always must
// indicate that a content property is invalid at the property level since developers may not actually implement
// the correct field validation in their property editors.
function serverValidationManagerCallback(isValid, propertyErrors, allErrors) {
var hadError = hasError;
hasError = !isValid;
if (hasError) {
// check if one of the errors is explicitly assigned to our validation key
hasServerError = _.some(propertyErrors, x => x.propertyAlias === propertyValidationKey);
if (hadError && hasServerError && propertyErrors.length === 1) {
// we're the only error remaining in the server validation
console.log("ONLY ERROR REMAINING " + propertyValidationKey);
}
//set the error message to the server message
scope.errorMsg = propertyErrors[0].errorMsg ? propertyErrors[0].errorMsg : labels.propertyHasErrors;
//flag that the current validator is invalid
console.log("valPropertyMsg invalid - " + propertyValidationKey);
formCtrl.$setValidity('valPropertyMsg', false, formCtrl);
startWatch();
}
else {
resetError();
}
}
unsubscribe.push(serverValidationManager.subscribe(
propertyValidationKey,
currentCulture,
"",
serverValidationManagerCallback,
currentSegment,
true // match property validation path prefix
));
}
});
}
//when the scope is disposed we need to unsubscribe
@@ -351,6 +295,8 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel
unsubscribe[u]();
}
});
onInit();
}

View File

@@ -82,6 +82,7 @@ function valServer(serverValidationManager) {
//clear the server validation entry
serverValidationManager.removePropertyError(propertyValidationPath, currentCulture, fieldName, currentSegment);
stopWatch();
}
}, true);

View File

@@ -375,7 +375,7 @@
* @name getBlockObject
* @methodOf umbraco.services.blockEditorModelObject
* @description Retrieve a Block Object for the given layout entry.
* The Block Object offers the nesecary data to display and edit a block.
* The Block Object offers the necessary data to display and edit a block.
* The Block Object setups live syncronization of content and settings models back to the data of your Property Editor model.
* The returned object, named ´BlockObject´, contains several usefull models to make editing of this block happen.
* The ´BlockObject´ contains the following properties:
@@ -448,6 +448,8 @@
// make basics from scaffold
blockObject.content = Utilities.copy(contentScaffold);
blockObject.content.udi = udi;
// Change the content.key to the GUID part of the udi, else it's just random which we don't want, it should be consistent
blockObject.content.key = udiService.getKey(udi);
mapToElementModel(blockObject.content, dataModel);

View File

@@ -52,6 +52,9 @@ function serverValidationManager($timeout) {
* Notifies all subscriptions again. Called when there are changes to subscriptions or errors.
*/
function notify() {
console.log("NOTIFY!");
$timeout(function () {
console.log(`VAL-ERROR-COUNT: ${items.length}`);
@@ -92,7 +95,8 @@ function serverValidationManager($timeout) {
//find all errors for this property
return _.filter(items, function (item) {
return ((matchPrefixValidationPath ? (item.propertyAlias === propertyAlias || propertyAlias.startsWith(item.propertyAlias + '/')) : item.propertyAlias === propertyAlias)
return ((matchPrefixValidationPath ? (item.propertyAlias === propertyAlias || (item.propertyAlias && item.propertyAlias.startsWith(propertyAlias + '/'))) : item.propertyAlias === propertyAlias)
//return ((matchPrefixValidationPath ? (item.propertyAlias === propertyAlias || propertyAlias.startsWith(item.propertyAlias + '/')) : item.propertyAlias === propertyAlias)
&& item.culture === culture
&& item.segment === segment
// ignore field matching if
@@ -115,29 +119,36 @@ function serverValidationManager($timeout) {
});
}
function notifyCallback(cb) {
if (cb.propertyAlias === null && cb.fieldName !== null) {
//its a field error callback
const fieldErrors = getFieldErrors(cb.fieldName);
const valid = fieldErrors.length === 0;
executeCallback(fieldErrors, cb.callback, cb.culture, cb.segment, valid);
}
else if (cb.propertyAlias != null) {
//its a property error
const propErrors = getPropertyErrors(cb.propertyAlias, cb.culture, cb.segment, cb.fieldName, cb.matchPrefix);
const valid = propErrors.length === 0;
executeCallback(propErrors, cb.callback, cb.culture, cb.segment, valid);
}
else {
//its a variant error
const variantErrors = getVariantErrors(cb.culture, cb.segment);
const valid = variantErrors.length === 0;
executeCallback(variantErrors, cb.callback, cb.culture, cb.segment, valid);
}
}
/** Call all registered callbacks indicating if the data they are subscribed to is valid or invalid */
function notifyCallbacks() {
callbacks.forEach(cb => {
if (cb.propertyAlias === null && cb.fieldName !== null) {
//its a field error callback
const fieldErrors = getFieldErrors(cb.fieldName);
const valid = fieldErrors.length === 0;
executeCallback(fieldErrors, cb.callback, cb.culture, cb.segment, valid);
}
else if (cb.propertyAlias != null) {
//its a property error
const propErrors = getPropertyErrors(cb.propertyAlias, cb.culture, cb.segment, cb.fieldName, cb.matchPrefix);
const valid = propErrors.length === 0;
executeCallback(propErrors, cb.callback, cb.culture, cb.segment, valid);
}
else {
//its a variant error
const variantErrors = getVariantErrors(cb.culture, cb.segment);
const valid = variantErrors.length === 0;
executeCallback(variantErrors, cb.callback, cb.culture, cb.segment, valid);
}
});
// nothing to call
if (items.length === 0) {
return;
}
callbacks.forEach(cb => notifyCallback(cb));
}
/**
@@ -343,29 +354,7 @@ function serverValidationManager($timeout) {
});
}
//find all errors for this item
var propertyErrors = getPropertyErrors(propertyAlias, culture, segment, fieldName);
var propertyPrefixErrors = getPropertyErrors(propertyAlias, culture, segment, fieldName, true);
//now call all of the call backs registered for this error
var cbs = getPropertyCallbacks(propertyAlias, culture, fieldName, segment);
//call each callback for this error
cbs.forEach(cb => {
if (cb.matchPrefix) {
executeCallback(propertyPrefixErrors, cb.callback, culture, segment, false);
}
else {
executeCallback(propertyErrors, cb.callback, culture, segment, false);
}
});
//execute variant specific callbacks here too when a propery error is added
var variantCbs = getVariantCallbacks(culture, segment);
//call each callback for this error
variantCbs.forEach(cb => {
executeCallback(propertyErrors, cb.callback, culture, segment, false);
});
notifyCallbacks();
}
/**
@@ -602,30 +591,33 @@ function serverValidationManager($timeout) {
segment = null;
}
if (propertyAlias === null) {
let cb = null;
callbacks.push({
if (propertyAlias === null) {
cb = {
propertyAlias: null,
culture: culture,
segment: segment,
fieldName: fieldName,
callback: callback,
id: id
});
};
}
else if (propertyAlias !== undefined) {
callbacks.push({
cb = {
propertyAlias: propertyAlias,
culture: culture,
culture: culture,
segment: segment,
fieldName: fieldName,
callback: callback,
id: id,
matchPrefix: matchValidationPathPrefix
});
};
}
callbacks.push(cb);
function unsubscribeId() {
//remove all callbacks for the content field
callbacks = _.reject(callbacks, function (item) {
@@ -633,9 +625,8 @@ function serverValidationManager($timeout) {
});
}
// Now notify the registrations for this callback if we've previously been notified and we're not cleared.
// This will happen for dynamically shown editors, like complex editors that load in sub element types.
notify();
// Notify the new callback
notifyCallback(cb);
//return a function to unsubscribe this subscription by uniqueId
return unsubscribeId;
@@ -733,7 +724,7 @@ function serverValidationManager($timeout) {
if (items.length !== count) {
// removal was successful, re-notify all subscribers
notify();
notifyCallbacks();
}
},
@@ -757,6 +748,10 @@ function serverValidationManager($timeout) {
return undefined;
},
getPropertyErrorsByValidationPath: function (propertyAlias, culture, segment) {
return getPropertyErrors(propertyAlias, culture, segment, "", true);
},
/**
* @ngdoc function
* @name getFieldError

View File

@@ -27,6 +27,21 @@
build: function (entityType, guid) {
return "umb://" + entityType + "/" + (guid.replace(/-/g, ""));
},
getKey: function (udi) {
if (!Utilities.isString(udi)) {
throw "udi is not a string";
}
if (!udi.startsWith("umb://")) {
throw "udi does not start with umb://";
}
var withoutScheme = udi.substr("umb://".length);
var withoutHost = withoutScheme.substr(withoutScheme.indexOf("/") + 1).trim();
if (withoutHost.length !== 32) {
throw "udi is not 32 chars";
}
return `${withoutHost.substr(0, 8)}-${withoutHost.substr(8, 4)}-${withoutHost.substr(12, 4)}-${withoutHost.substr(16, 4)}-${withoutHost.substr(20)}`;
}
}
}

View File

@@ -13,7 +13,7 @@
data-element="property-{{property.alias}}"
ng-repeat="property in group.properties track by property.alias"
property="property"
element-udi="{{vm.model.udi}}"
element-key="{{vm.model.key}}"
show-inherit="vm.model.variants.length > 1 && !property.culture && !activeVariant.language.isDefault"
inherits-from="defaultVariant.language.name">

View File

@@ -3,7 +3,7 @@
<umb-property property="property"
property-alias="{{property.propertyAlias}}"
element-udi="{{model.key}}"
element-key="{{model.key}}"
ng-class="{'umb-nested-content--not-supported': property.notSupported, 'umb-nested-content--mandatory': property.ncMandatory}"
data-element="property-{{property.alias}}">

View File

@@ -1,9 +1,11 @@
describe('serverValidationManager tests', function () {
var serverValidationManager;
var $rootScope, serverValidationManager, $timeout;
beforeEach(module('umbraco.services'));
beforeEach(inject(function ($injector) {
$rootScope = $injector.get('$rootScope');
$timeout = $injector.get('$timeout');
serverValidationManager = $injector.get('serverValidationManager');
serverValidationManager.clear();
}));
@@ -480,7 +482,9 @@
allErrors: allErrors
};
}, null);
//act
serverValidationManager.addFieldError("Name", "Required");
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null);
@@ -501,7 +505,9 @@
var cb2 = function () {
};
serverValidationManager.subscribe(null, null, "Name", cb1, null);
serverValidationManager.subscribe(null, null, "Title", cb2, null);
//act
serverValidationManager.addFieldError("Name", "Required");
@@ -537,6 +543,7 @@
};
}, null);
serverValidationManager.subscribe("myProperty", null, "", function (isValid, propertyErrors, allErrors) {
numCalled++;
args2 = {
@@ -545,6 +552,9 @@
allErrors: allErrors
};
}, null);
console.log("NOW ADDING ERRORS " + numCalled);
//act
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null);
@@ -587,6 +597,7 @@
allErrors: allErrors
};
}, null);
serverValidationManager.subscribe(null, "es-ES", null, function (isValid, propertyErrors, allErrors) {
numCalled++;
@@ -596,6 +607,7 @@
allErrors: allErrors
};
}, null);
//act
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1", null);
@@ -604,28 +616,30 @@
serverValidationManager.addPropertyError("myProperty", "fr-FR", "", "Some value 3", null);
//assert
expect(args1).not.toBeUndefined();
expect(args1.isValid).toBe(false);
expect(args2.isValid).toBe(true); // no errors registered for this callback
expect(args2).toBeUndefined();
expect(numCalled).toEqual(2);
expect(numCalled).toEqual(8); // both subscriptions will be called once per addPropertyError
});
it('can subscribe to a property validation path prefix', function () {
var numCalled = 0;
var callbackA = [];
var callbackB = [];
//arrange
serverValidationManager.subscribe("myProperty", null, null, function (isValid, propertyErrors, allErrors) {
numCalled++;
// since this is matching on prefix, there should be as many property errors as numCalled
expect(propertyErrors.length).toEqual(numCalled);
callbackA.push(propertyErrors);
}, null, true);
serverValidationManager.subscribe("myProperty/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses", null, null, function (isValid, propertyErrors, allErrors) {
callbackB.push(propertyErrors);
}, null, true);
//act
// will match:
// will match A:
serverValidationManager.addPropertyError("myProperty", null, null, "property error", null);
serverValidationManager.addPropertyError("myProperty", null, "value1", "value error", null);
// will match A + B
serverValidationManager.addPropertyError("myProperty/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1/city", null, null, "property error", null);
serverValidationManager.addPropertyError("myProperty/34E3A26C-103D-4A05-AB9D-7E14032309C3/addresses/FBEAEE8F-4BC9-43EE-8B81-FCA8978850F1/city", null, "value1", "value error", null);
// won't match:
@@ -635,7 +649,25 @@
serverValidationManager.addPropertyError("otherProperty", null, "value1", "value error", null);
//assert
expect(numCalled).toEqual(4);
// both will be called each time addPropertyError is called
expect(callbackA.length).toEqual(8);
expect(callbackB.length).toEqual(8);
expect(callbackA[callbackA.length - 1].length).toEqual(4); // 4 errors for A
expect(callbackB[callbackB.length - 1].length).toEqual(2); // 2 errors for B
// clear the data and notify
callbackA = [];
callbackB = [];
serverValidationManager.notify();
$timeout.flush();
expect(callbackA.length).toEqual(1);
expect(callbackB.length).toEqual(1);
expect(callbackA[0].length).toEqual(4); // 4 errors for A
expect(callbackB[0].length).toEqual(2); // 2 errors for B
});
// TODO: Finish testing the rest!