diff --git a/src/Framework/Framework/Binding/BindingHelper.cs b/src/Framework/Framework/Binding/BindingHelper.cs index bc0c1c87c4..23228016f0 100644 --- a/src/Framework/Framework/Binding/BindingHelper.cs +++ b/src/Framework/Framework/Binding/BindingHelper.cs @@ -588,10 +588,43 @@ public override string Message { get { - var actualContextsHelp = - ActualContextTypes is null ? "" : - $" Real data context types: {string.Join(", ", ActualContextTypes.Select(t => t?.ToCode(stripNamespace: true) ?? "null"))}."; - return $"Could not find DataContext space of '{ContextObject}'. The DataContextType property of the binding does not correspond to DataContextType of the {Control.GetType().Name} nor any of its ancestors. Control's context is {ControlContext}, binding's context is {BindingContext}." + actualContextsHelp; + var message = new StringBuilder() + .Append($"Could not find DataContext space of '{ContextObject}'. The DataContextType property of the binding does not correspond to DataContextType of the {Control.GetType().Name} nor any of its ancestors."); + + var stackComparison = DataContextStack.CompareStacksMessage(ControlContext, BindingContext); + + for (var i = 0; i < stackComparison.Length; i++) + { + var level = i switch { + 0 => "_this: ", + 1 => "_parent: ", + _ => $"_parent{i}: " + }; + + message.Append($"\nControl {level}"); + foreach (var (control, binding) in stackComparison[i]) + { + var length = Math.Max(control.Length, binding.Length); + if (control == binding) + message.Append(control); + else + message.Append(StringUtils.PadCenter(StringUtils.UnicodeUnderline(control), length + 2)); + } + + message.Append($"\nBinding {level}"); + foreach (var (control, binding) in stackComparison[i]) + { + var length = Math.Max(control.Length, binding.Length); + if (control == binding) + message.Append(binding); + else + message.Append(StringUtils.PadCenter(StringUtils.UnicodeUnderline(binding), length + 2)); + } + } + + if (ActualContextTypes is {}) + message.Append($"\nReal data context types: {string.Join(", ", ActualContextTypes.Select(t => t?.ToCode(stripNamespace: true) ?? "null"))}"); + return message.ToString(); } } } diff --git a/src/Framework/Framework/Compilation/ControlTree/DataContextStack.cs b/src/Framework/Framework/Compilation/ControlTree/DataContextStack.cs index 63bf1031d7..1e506c8be1 100644 --- a/src/Framework/Framework/Compilation/ControlTree/DataContextStack.cs +++ b/src/Framework/Framework/Compilation/ControlTree/DataContextStack.cs @@ -176,18 +176,20 @@ int ComputeHashCode() } } - public override string ToString() - { - string?[] features = new [] { - $"type={this.DataContextType.ToCode()}", - this.ServerSideOnly ? "server-side-only" : null, - this.NamespaceImports.Any() ? "imports=[" + string.Join(", ", this.NamespaceImports) + "]" : null, - this.ExtensionParameters.Any() ? "ext=[" + string.Join(", ", this.ExtensionParameters.Select(e => e.Identifier + ": " + e.ParameterType.CSharpName)) + "]" : null, - this.BindingPropertyResolvers.Any() ? "resolvers=[" + string.Join(", ", this.BindingPropertyResolvers.Select(s => s.Method)) + "]" : null, - this.Parent != null ? "par=[" + string.Join(", ", this.Parents().Select(p => p.ToCode(stripNamespace: true))) + "]" : null - }; - return "(" + features.Where(a => a != null).StringJoin(", ") + ")"; - } + private string?[] ToStringFeatures() => [ + $"type={this.DataContextType.ToCode()}", + this.ServerSideOnly ? "server-side-only" : null, + this.NamespaceImports.Any() ? "imports=[" + string.Join(", ", this.NamespaceImports) + "]" : null, + this.ExtensionParameters.Any() ? "ext=[" + string.Join(", ", this.ExtensionParameters.Select(e => e.Identifier + ": " + e.ParameterType.CSharpName)) + "]" : null, + this.BindingPropertyResolvers.Any() ? "resolvers=[" + string.Join(", ", this.BindingPropertyResolvers.Select(s => s.Method)) + "]" : null, + this.Parent != null ? "par=[" + string.Join(", ", this.Parents().Select(p => p.ToCode(stripNamespace: true))) + "]" : null + ]; + + public override string ToString() => + "(" + ToStringFeatures().WhereNotNull().StringJoin(", ") + ")"; + + private string ToStringWithoutParent() => + ToStringFeatures()[..^1].WhereNotNull().StringJoin(", "); //private static ConditionalWeakTable internCache = new ConditionalWeakTable(); @@ -212,7 +214,7 @@ public static DataContextStack CreateCollectionElement(Type elementType, bool serverSideOnly = false) { var indexParameters = new CollectionElementDataContextChangeAttribute(0).GetExtensionParameters(new ResolvedTypeDescriptor(elementType.MakeArrayType())); - extensionParameters = extensionParameters is null ? indexParameters.ToArray() : extensionParameters.Concat(indexParameters).ToArray(); + extensionParameters = [..(extensionParameters ?? []), ..indexParameters ]; return DataContextStack.Create( elementType, parent, imports: imports, @@ -221,5 +223,139 @@ public static DataContextStack CreateCollectionElement(Type elementType, serverSideOnly: serverSideOnly ); } + + private static int Difference(DataContextStack a, DataContextStack b) + { + if (a == b) return 0; + + var result = 0; + if (a.DataContextType != b.DataContextType) + result += 6; + + if (a.DataContextType.Namespace != b.DataContextType.Namespace) + result += 2; + + if (a.DataContextType.Name != b.DataContextType.Name) + result += 2; + + result += CompareSets(a.NamespaceImports, b.NamespaceImports); + + result += CompareSets(a.ExtensionParameters, b.ExtensionParameters); + + result += CompareSets(a.BindingPropertyResolvers, b.BindingPropertyResolvers); + + if (a.Parent != b.Parent) + result += 1; + + return result; + + + static int CompareSets(IEnumerable a, IEnumerable b) + { + return a.Union(b).Count() - a.Intersect(b).Count(); + } + } + + public static (string a, string b)[][] CompareStacksMessage(DataContextStack a, DataContextStack b) + { + var alignment = StringSimilarity.SequenceAlignment( + a.EnumerableItems().ToArray().AsSpan(), b.EnumerableItems().ToArray().AsSpan(), + Difference, + gapCost: 10); + + return alignment.Select(pair => { + return CompareMessage(pair.a, pair.b); + }).ToArray(); + } + + /// Provides a formatted string for two DataContextStacks with aligned fragments used for highlighting. Does not include the parent context. + public static (string a, string b)[] CompareMessage(DataContextStack? a, DataContextStack? b) + { + if (a == null || b == null) return new[] { (a?.ToStringWithoutParent() ?? "(missing)", b?.ToStringWithoutParent() ?? "(missing)") }; + + var result = new List<(string, string)>(); + + void same(string str) => result.Add((str, str)); + void different(string? a, string? b) => result.Add((a ?? "", b ?? "")); + + same("type="); + if (a.DataContextType == b.DataContextType) + same(a.DataContextType.ToCode(stripNamespace: true)); + else + { + different(a.DataContextType.Namespace, b.DataContextType.Namespace); + same("."); + different(a.DataContextType.ToCode(stripNamespace: true), b.DataContextType.ToCode(stripNamespace: true)); + } + + if (a.ServerSideOnly || b.ServerSideOnly) + { + same(", "); + different(a.ServerSideOnly ? "server-side-only" : "", b.ServerSideOnly ? "server-side-only" : ""); + } + + if (a.NamespaceImports.Any() || b.NamespaceImports.Any()) + { + same(", imports=["); + var importsAligned = StringSimilarity.SequenceAlignment( + a.NamespaceImports.AsSpan(), b.NamespaceImports.AsSpan(), + (a, b) => a.Equals(b) ? 0 : + a.Namespace == b.Namespace || a.Alias == b.Alias ? 1 : + 3, + gapCost: 2); + foreach (var (i, (aImport, bImport)) in importsAligned.Indexed()) + { + if (i > 0) + same(", "); + + different(aImport.ToString(), bImport.ToString()); + } + + same("]"); + } + + if (a.ExtensionParameters.Any() || b.ExtensionParameters.Any()) + { + same(", ext=["); + var extAligned = StringSimilarity.SequenceAlignment( + a.ExtensionParameters.AsSpan(), b.ExtensionParameters.AsSpan(), + (a, b) => a.Equals(b) ? 0 : + a.Identifier == b.Identifier ? 1 : + 3, + gapCost: 2); + foreach (var (i, (aExt, bExt)) in extAligned.Indexed()) + { + if (i > 0) + same(", "); + + if (Equals(aExt, bExt)) + same(aExt!.Identifier); + else if (aExt is null) + different("", bExt!.Identifier + ": " + bExt.ParameterType.CSharpName); + else if (bExt is null) + different(aExt.Identifier + ": " + aExt.ParameterType.CSharpName, ""); + else + { + different(aExt.Identifier, bExt.Identifier); + same(": "); + if (aExt.ParameterType.IsEqualTo(bExt.ParameterType)) + same(aExt.ParameterType.CSharpName); + else + different(aExt.ParameterType.CSharpFullName, bExt.ParameterType.CSharpFullName); + + if (aExt.Identifier == bExt.Identifier && aExt.GetType() != bExt.GetType()) + { + same(" ("); + different(aExt.GetType().ToCode(), bExt.GetType().ToCode()); + same(")"); + } + } + } + + same("]"); + } + + return result.ToArray(); + } } } diff --git a/src/Framework/Framework/Hosting/ErrorPages/ErrorFormatter.cs b/src/Framework/Framework/Hosting/ErrorPages/ErrorFormatter.cs index a1bd315b35..03cd366599 100644 --- a/src/Framework/Framework/Hosting/ErrorPages/ErrorFormatter.cs +++ b/src/Framework/Framework/Hosting/ErrorPages/ErrorFormatter.cs @@ -350,7 +350,7 @@ public string ErrorHtml(Exception exception, IHttpContext context) .ToArray()!, errorCode: context.Response.StatusCode, errorDescription: "Unhandled exception occurred", - summary: exception.GetType().FullName + ": " + exception.Message.LimitLength(600), + summary: exception.GetType().FullName + ": " + exception.Message.LimitLength(3000), context: DotvvmRequestContext.TryGetCurrent(context), exception: exception); diff --git a/src/Framework/Framework/Utils/StringSimilarity.cs b/src/Framework/Framework/Utils/StringSimilarity.cs index 58757a56f0..3f696302bb 100644 --- a/src/Framework/Framework/Utils/StringSimilarity.cs +++ b/src/Framework/Framework/Utils/StringSimilarity.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; namespace DotVVM.Framework.Utils { @@ -44,5 +46,110 @@ public static int DamerauLevenshteinDistance(string a, string b) static int min(int a, int b) => Math.Min(a, b); static int min(int a, int b, int c) => min(min(a, b), c); + + + public static (T? a, T? b)[] SequenceAlignment(ReadOnlySpan a, ReadOnlySpan b, Func substituionCost, int gapCost = 10) + { + // common case: strings are almost equal + // -> skip same prefix and suffix since the rest of the algorithm is quadratic + + var prefix = new List<(T?, T?)>(); + for (var i = 0; i < min(a.Length, b.Length); i++) + { + if (substituionCost(a[i], b[i]) <= 0) + prefix.Add((a[i], b[i])); + else + break; + } + a = a.Slice(prefix.Count); + b = b.Slice(prefix.Count); + // Console.WriteLine("Prefix length: " + prefix.Count); + + var suffix = new List<(T?, T?)>(); + + for (var i = 1; i <= min(a.Length, b.Length); i++) + { + if (substituionCost(a[^i], b[^i]) <= 0) + suffix.Add((a[^i], b[^i])); + else + break; + } + a = a.Slice(0, a.Length - suffix.Count); + b = b.Slice(0, b.Length - suffix.Count); + // Console.WriteLine("Suffix length: " + suffix.Count); + + var d = new int[a.Length + 1, b.Length + 1]; + var arrows = new sbyte[a.Length + 1, b.Length + 1]; + for (var i = 0; i <= a.Length; i++) + d[i, 0] = i * gapCost; + + for (var j = 0; j <= b.Length; j++) + d[0, j] = j * gapCost; + + for (var i = 0; i < a.Length; i ++) + { + for (var j = 0; j < b.Length; j++) + { + var substitutionCost = substituionCost(a[i], b[j]); + var dist = d[i, j] + substitutionCost; + sbyte arrow = 0; // record which direction is optimal + if (dist > d[i, j+1] + gapCost) + { + dist = d[i, j+1] + gapCost; + arrow = -1; + } + if (dist > d[i+1, j] + gapCost) + { + dist = d[i+1, j] + gapCost; + arrow = 1; + } + + d[i+1, j+1] = dist; + arrows[i+1, j+1] = arrow; + } + } + + // for (int i = 0; i <= a.Length; i++) + // { + // Console.WriteLine("D: " + string.Join("\t", Enumerable.Range(0, b.Length + 1).Select(j => d[i, j]))); + // Console.WriteLine("A: " + string.Join("\t", Enumerable.Range(0, b.Length + 1).Select(j => arrows[i, j] switch { 0 => "↖", 1 => "←", -1 => "↑" }))); + // } + + + // follow arrows from the back + for (int i = a.Length, j = b.Length; i > 0 || j > 0;) + { + // we are on border + if (i == 0) + { + j--; + suffix.Add((default, b[j])); + } + else if (j == 0) + { + i--; + suffix.Add((a[i], default)); + } + else if (arrows[i, j] == 0) + { + i--; + j--; + suffix.Add((a[i], b[j])); + } + else if (arrows[i, j] == 1) + { + j--; + suffix.Add((default, b[j])); + } + else if (arrows[i, j] == -1) + { + i--; + suffix.Add((a[i], default)); + } + } + + suffix.Reverse(); + return [..prefix, ..suffix]; + } } } diff --git a/src/Framework/Framework/Utils/StringUtils.cs b/src/Framework/Framework/Utils/StringUtils.cs index bbc7a46dea..2e82dc2d1d 100644 --- a/src/Framework/Framework/Utils/StringUtils.cs +++ b/src/Framework/Framework/Utils/StringUtils.cs @@ -1,5 +1,8 @@ using System; +using System.Buffers; using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Text; using DotVVM.Framework.Binding; @@ -56,6 +59,150 @@ public static string LimitLength(this string source, int length, string ending = } } + public static string CreateString(int length, T state, SpanAction action) + { +#if DotNetCore + return string.Create(length, state, action); +#else + var buffer = ArrayPool.Shared.Rent(length); + try + { + action(buffer.AsSpan(0, length), state); + return new string(buffer, 0, length); + } + finally + { + ArrayPool.Shared.Return(buffer); + } +#endif + } + + // /// Appends to each grapheme cluster of the source string. + // internal static string AddCombinerToGraphemes(string source, string combiner, bool includeLast = true) + // { + // if (source.Length == 0) + // return ""; + + // var buffer = ArrayPool.Shared.Rent(source.Length * (1 + combiner.Length)); + // var bufferIndex = 0; + + // var enumerator = StringInfo.GetTextElementEnumerator(source); + // // enumerator.ElementIndex points to the element start, but we also need the end index, so the loop is "one element behind" the enumerator + // Debug.Assert(enumerator.MoveNext()); + // var lastIndex = enumerator.ElementIndex; + // while (enumerator.MoveNext()) + // { + // var length = enumerator.ElementIndex - lastIndex; + // source.AsSpan(lastIndex, length).CopyTo(buffer.AsSpan(bufferIndex)); + + // combiner.CopyTo(0, buffer, bufferIndex + length, combiner.Length); + // lastIndex = enumerator.ElementIndex; + // bufferIndex += length + combiner.Length; + // } + + // // last element + // source.AsSpan(lastIndex).CopyTo(buffer.AsSpan(bufferIndex)); + // bufferIndex += source.Length - lastIndex; + // if (includeLast) + // { + // combiner.CopyTo(0, buffer, bufferIndex, combiner.Length); + // bufferIndex += combiner.Length; + // } + + // var result = new string(buffer, 0, bufferIndex); + // ArrayPool.Shared.Return(buffer); + // return result; + // } + + public static string UnicodeUnderline(string source) + { + var result = new StringBuilder(); + char zwSpace = '\u200B'; + char lowLine = '\u0332'; + char doubleMacron = '\u035F'; + char macron = '\u0331'; + + var enumerator = StringInfo.GetTextElementEnumerator(source); + bool isFirst = true; + // result.Append(zwSpace); + // result.Append(macron); + while (enumerator.MoveNext()) + { + var element = enumerator.GetTextElement(); + result.Append(element); + + // we want s͟t͟r͟i͟n͟g not s͟t͟r͟i͟n͟g͟ + var sticksDown = element switch { + "q" or "y" or "g" or "p" or "j" => true, + _ => false + }; + if (sticksDown) + { + // result.Append(zwSpace); + // result.Append(macron); + // ignore, next time put lowLine again + isFirst = true; + } + else if (isFirst) + { + // result.Append(lowLine); + result.Append(doubleMacron); + isFirst = false; + } + else + { + result.Append(doubleMacron); + } + } + return result.ToString(); + } + + public static string UnicodeBold(string source) + { + var decomposed = source.Normalize(NormalizationForm.FormD); + var result = new StringBuilder(); + + foreach (var ch in decomposed) + { + if (ch >= 'A' && ch <= 'Z') + { + result.Append("𝗔"[0]); // surrogate pair + result.Append((char)("𝗔"[1] + (ch - 'a'))); + } + else if (ch >= 'a' && ch <= 'z') + { + result.Append("𝗮"[0]); // surrogate pair + result.Append((char)("𝗮"[1] + (ch - 'a'))); + } + else if (ch >= '0' && ch <= '9') + { + result.Append("𝟬"[0]); // surrogate pair + result.Append((char)("𝟬"[1] + (ch - 'a'))); + } + else + { + result.Append(ch); + } + } + return result.ToString(); + } + + public static string PadCenter(string str, int length) + { + var charBoundaries = StringInfo.ParseCombiningCharacters(str); + if (charBoundaries.Length >= length) + return str; + + return CreateString(str.Length + length - charBoundaries.Length, (str, length, charBoundaries), (span, state) => + { + var (str, length, charBoundaries) = state; + var start = (length - charBoundaries.Length) / 2; + span.Slice(0, start).Fill(' '); + str.AsSpan().CopyTo(span.Slice(start)); + span.Slice(start + str.Length).Fill(' '); + }); + } + internal static string DotvvmInternString(this char ch, string? str = null) => ch switch { ' ' => " ", diff --git a/src/Tests/Runtime/RuntimeErrorTests.cs b/src/Tests/Runtime/RuntimeErrorTests.cs index aabc23d5bc..b8379df0d0 100644 --- a/src/Tests/Runtime/RuntimeErrorTests.cs +++ b/src/Tests/Runtime/RuntimeErrorTests.cs @@ -22,6 +22,7 @@ using DotVVM.Framework.Compilation; using DotVVM.Framework.Testing; using System.Threading.Tasks; +using DotVVM.Framework.Compilation.ControlTree.Resolved; namespace DotVVM.Framework.Tests.Runtime { @@ -134,15 +135,47 @@ public void DataContextStack_ToString() check.CheckString(d.ToString()); } - [TestMethod] - public void CantFindDataContextSpace() + private void TriggerDataContextMismatchError(DataContextStack type1, DataContextStack type2) { _ = HtmlGenericControl.VisibleProperty; // runtime hack for static construction - var binding = ValueBindingExpression.CreateBinding(bindingService, a => false, DataContextStack.Create(typeof(string))); + var binding = ValueBindingExpression.CreateBinding(bindingService, a => false, type1); var control = new HtmlGenericControl("div"); - control.SetValue(Internal.DataContextTypeProperty, DataContextStack.Create(typeof(string), DataContextStack.Create(typeof(int)))); + control.SetValue(Internal.DataContextTypeProperty, type2); control.SetProperty(c => c.Visible, binding); - check.CheckException(() => control.Visible.ToString()); + control.Visible.ToString(); + } + + [TestMethod] + public void CantFindDataContextSpace() + { + check.CheckException(() => TriggerDataContextMismatchError( + DataContextStack.Create(typeof(string)), + DataContextStack.Create(typeof(string), DataContextStack.Create(typeof(int)))) + ); + } + + [TestMethod] + public void CantFindDataContextSpace_NSDifference() + { + var root = DataContextStack.Create(typeof(Binding.TestViewModel3), extensionParameters: [ new CurrentMarkupControlExtensionParameter(new ResolvedTypeDescriptor(typeof(DotvvmMarkupControl))) ]); + var type1 = DataContextStack.Create(typeof(Binding.TestViewModel), root); + var type2 = DataContextStack.Create(typeof(Runtime.ControlTree.TestViewModel), DataContextStack.Create(typeof(string), root)); + check.CheckException(() => TriggerDataContextMismatchError(type1, type2)); + } + [TestMethod] + public void CantFindDataContextSpace_ParentChild() + { + var type1 = DataContextStack.Create(typeof(Binding.TestViewModel)); + var type2 = DataContextStack.CreateCollectionElement(typeof(int), type1); + check.CheckException(() => TriggerDataContextMismatchError(type1, type2)); + } + [TestMethod] + public void CantFindDataContextSpace_MissingEP() + { + var root = DataContextStack.Create(typeof(Binding.TestViewModel)); + var type1 = DataContextStack.CreateCollectionElement(typeof(int), root, extensionParameters: [ new InjectedServiceExtensionParameter("services", new ResolvedTypeDescriptor(typeof(IServiceProvider))) ]); + var type2 = DataContextStack.CreateCollectionElement(typeof(int), root); + check.CheckException(() => TriggerDataContextMismatchError(type1, type2)); } } } diff --git a/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace.txt b/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace.txt index 1178fad778..6eb90ea910 100644 --- a/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace.txt +++ b/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace.txt @@ -1,2 +1,7 @@ -InvalidDataContextTypeException occurred: Could not find DataContext space of '{value: False}'. The DataContextType property of the binding does not correspond to DataContextType of the HtmlGenericControl nor any of its ancestors. Control's context is (type=string, par=[int]), binding's context is (type=string). Real data context types: . +InvalidDataContextTypeException occurred: Could not find DataContext space of '{value: False}'. The DataContextType property of the binding does not correspond to DataContextType of the HtmlGenericControl nor any of its ancestors. +Control _this: type=string +Binding _this: type=string +Control _parent: t͟ype͟=͟i͟n͟t͟ +Binding _parent: (͟m͟i͟s͟s͟i͟n͟g)͟ +Real data context types: at object DotVVM.Framework.Controls.DotvvmBindableObject.EvalPropertyValue(DotvvmProperty property, object value) diff --git a/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace_MissingEP.txt b/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace_MissingEP.txt new file mode 100644 index 0000000000..33e4c22510 --- /dev/null +++ b/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace_MissingEP.txt @@ -0,0 +1,7 @@ +InvalidDataContextTypeException occurred: Could not find DataContext space of '{value: False}'. The DataContextType property of the binding does not correspond to DataContextType of the HtmlGenericControl nor any of its ancestors. +Control _this: type=int, ext=[ , _index, _collection] +Binding _this: type=int, ext=[ s͟e͟r͟v͟i͟c͟e͟s͟:͟ ͟I͟S͟e͟r͟v͟i͟c͟e͟P͟r͟o͟v͟i͟d͟e͟r͟ , _index, _collection] +Control _parent: type=TestViewModel +Binding _parent: type=TestViewModel +Real data context types: + at object DotVVM.Framework.Controls.DotvvmBindableObject.EvalPropertyValue(DotvvmProperty property, object value) diff --git a/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace_NSDifference.txt b/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace_NSDifference.txt new file mode 100644 index 0000000000..fc5f6b0a54 --- /dev/null +++ b/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace_NSDifference.txt @@ -0,0 +1,9 @@ +InvalidDataContextTypeException occurred: Could not find DataContext space of '{value: False}'. The DataContextType property of the binding does not correspond to DataContextType of the HtmlGenericControl nor any of its ancestors. +Control _this: type= D͟o͟t͟V͟V͟M͟.͟F͟r͟a͟m͟e͟w͟o͟r͟k͟.͟T͟e͟s͟t͟s͟.͟R͟u͟n͟t͟i͟m͟e͟.͟C͟o͟n͟t͟r͟o͟l͟T͟r͟e͟e͟ .TestViewModel +Binding _this: type= D͟o͟t͟V͟V͟M͟.͟F͟r͟a͟m͟e͟w͟o͟r͟k͟.͟T͟e͟s͟t͟s͟.͟B͟i͟n͟d͟i͟n͟g .TestViewModel +Control _parent: t͟ype͟=͟s͟t͟r͟i͟n͟g +Binding _parent: (͟m͟i͟s͟s͟i͟n͟g)͟ +Control _parent2: type=TestViewModel3, ext=[_control] +Binding _parent2: type=TestViewModel3, ext=[_control] +Real data context types: + at object DotVVM.Framework.Controls.DotvvmBindableObject.EvalPropertyValue(DotvvmProperty property, object value) diff --git a/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace_ParentChild.txt b/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace_ParentChild.txt new file mode 100644 index 0000000000..9836e3557d --- /dev/null +++ b/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace_ParentChild.txt @@ -0,0 +1,7 @@ +InvalidDataContextTypeException occurred: Could not find DataContext space of '{value: False}'. The DataContextType property of the binding does not correspond to DataContextType of the HtmlGenericControl nor any of its ancestors. +Control _this: t͟ype͟=͟i͟n͟t͟,͟ ͟e͟x͟t͟=͟[͟_͟i͟n͟d͟e͟x͟:͟ ͟i͟n͟t͟,͟ ͟_͟c͟o͟l͟l͟e͟c͟t͟i͟o͟n͟:͟ ͟B͟i͟n͟d͟i͟n͟gC͟o͟l͟l͟e͟c͟t͟i͟o͟n͟I͟n͟f͟o͟]͟ +Binding _this: (͟m͟i͟s͟s͟i͟n͟g)͟ +Control _parent: type=TestViewModel +Binding _parent: type=TestViewModel +Real data context types: + at object DotVVM.Framework.Controls.DotvvmBindableObject.EvalPropertyValue(DotvvmProperty property, object value)