public void PropertyChangedTest() { DocumentDataModel model = new DocumentDataModel(); string[] watchedProperties = new string[] { "HasUnsavedData", "HasUndoData", "HasRedoData", "DocumentRoot", "ObservableDocumentRoot" }; Dictionary <string, object> propertyValues = new Dictionary <string, object>(); HashSet <string> changedProperties = new HashSet <string>(); PropertyChangedEventHandler propChangedHandler = delegate(object sender, PropertyChangedEventArgs e) { Assert.AreSame(model, sender); changedProperties.Add(e.PropertyName); }; ZeroArgDelegate prepare = delegate() { // Note old property values for later comparison. foreach (string prop in watchedProperties) { propertyValues[prop] = model.GetType().GetProperty(prop).GetValue(model, null); } changedProperties.Clear(); }; ZeroArgDelegate verify = delegate() { // Verify that properties are unchanged, or that we were notified that they changed. foreach (string prop in watchedProperties) { if (changedProperties.Contains(prop)) { continue; // We were notified of a change. } object oldValue = propertyValues[prop]; object newValue = model.GetType().GetProperty(prop).GetValue(model, null); Assert.AreEqual(oldValue, newValue, "Property {0} changed without notification, from '{1}' to '{2}'", prop, oldValue, newValue); } DocumentDataModelInvariant(model); }; model.PropertyChanged += propChangedHandler; prepare(); model.New(new Size(100, 100), new Thickness(20)); verify(); // New should trigger updates on both DocumentRoot properties. Assert.IsTrue(changedProperties.Contains("DocumentRoot")); Assert.IsTrue(changedProperties.Contains("ObservableDocumentRoot")); prepare(); model.AddShape(new XElement("Test")); verify(); // A "deep" change of the document XML must not trigger updates on DocumentRoot, but only on ObservableDocumentRoot Assert.IsFalse(changedProperties.Contains("DocumentRoot")); Assert.IsTrue(changedProperties.Contains("ObservableDocumentRoot")); prepare(); model.Undo(); verify(); // Undo should trigger updates on both DocumentRoot properties. Assert.IsTrue(changedProperties.Contains("DocumentRoot")); Assert.IsTrue(changedProperties.Contains("ObservableDocumentRoot")); prepare(); model.Redo(); verify(); // Redo should trigger updates on both DocumentRoot properties. Assert.IsTrue(changedProperties.Contains("DocumentRoot")); Assert.IsTrue(changedProperties.Contains("ObservableDocumentRoot")); prepare(); ((XElement)model.DocumentRoot.FirstNode).Name = "Test2"; verify(); // A "deep" change of the document XML must not trigger updates on DocumentRoot, but only on ObservableDocumentRoot Assert.IsFalse(changedProperties.Contains("DocumentRoot")); Assert.IsTrue(changedProperties.Contains("ObservableDocumentRoot")); // If we remove the handler, we should not get any notifications. :-) model.PropertyChanged -= propChangedHandler; prepare(); model.Undo(); Assert.AreEqual(0, changedProperties.Count); // No changes that we know of. }