diff --git a/src/Framework/Framework/Binding/BindingHelper.cs b/src/Framework/Framework/Binding/BindingHelper.cs index b69e947c37..afa507787d 100644 --- a/src/Framework/Framework/Binding/BindingHelper.cs +++ b/src/Framework/Framework/Binding/BindingHelper.cs @@ -97,7 +97,30 @@ internal static (int stepsUp, DotvvmBindableObject target) FindDataContextTarget { // only count changes which are visible client-side // server-side context are not present in the client-side stack at all, so we need to skip them here - changes++; + + // don't count changes which only extend the data context, but don't nest it + + var isNesting = ancestorContext.IsAncestorOf(lastAncestorContext); + if (isNesting) + { + changes++; + } +#if DEBUG + else if (!lastAncestorContext.DataContextType.IsAssignableFrom(ancestorContext.DataContextType)) + { + // this should not happen - data context type should not randomly change without nesting. + // we change data context stack when we get into different compilation context - a markup control + // but that will be always the same viewmodel type (or supertype) + + var previousAncestor = control.GetAllAncestors(includingThis: true).TakeWhile(aa => aa != a).LastOrDefault(); + var config = (control.GetValue(Internal.RequestContextProperty) as Hosting.IDotvvmRequestContext)?.Configuration; + throw new DotvvmControlException( + previousAncestor ?? a, + $"DataContext type changed from '{lastAncestorContext.DataContextType.ToCode()}' to '{ancestorContext.DataContextType.ToCode()}' without nesting. " + + $"{previousAncestor?.DebugString(config)} has DataContext: {lastAncestorContext}, " + + $"{a.DebugString(config)} has DataContext: {ancestorContext}"); + } +#endif lastAncestorContext = ancestorContext; } diff --git a/src/Framework/Framework/Compilation/ControlTree/DataContextStack.cs b/src/Framework/Framework/Compilation/ControlTree/DataContextStack.cs index 36cf4ef486..599f65b1ad 100644 --- a/src/Framework/Framework/Compilation/ControlTree/DataContextStack.cs +++ b/src/Framework/Framework/Compilation/ControlTree/DataContextStack.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -108,6 +108,21 @@ public IEnumerable Parents() } } + public bool IsAncestorOf(DataContextStack x) + { + var c = x.Parent; + while (c != null) + { + if (this.hashCode == c.hashCode) + { + if (this.Equals(c)) + return true; + } + c = c.Parent; + } + return false; + } + ITypeDescriptor IDataContextStack.DataContextType => new ResolvedTypeDescriptor(DataContextType); IDataContextStack? IDataContextStack.Parent => Parent; diff --git a/src/Samples/Tests/Tests/Complex/TaskListTests.cs b/src/Samples/Tests/Tests/Complex/TaskListTests.cs index 09fd44c01d..dd6e74735e 100644 --- a/src/Samples/Tests/Tests/Complex/TaskListTests.cs +++ b/src/Samples/Tests/Tests/Complex/TaskListTests.cs @@ -51,7 +51,7 @@ public void Complex_TaskList_ServerRenderedTaskList() //add task browser.SendKeys("input[type=text]", "DotVVM"); - browser.Click("input[type=button]"); + browser.Click("input[type=submit]"); browser.FindElements(".table tr").ThrowIfDifferentCountThan(4); diff --git a/src/Tests/ControlTests/MarkupControlTests.cs b/src/Tests/ControlTests/MarkupControlTests.cs index 1c34ed9b15..ee0dc8c46d 100644 --- a/src/Tests/ControlTests/MarkupControlTests.cs +++ b/src/Tests/ControlTests/MarkupControlTests.cs @@ -27,6 +27,7 @@ public class MarkupControlTests _ = Repeater.RenderAsNamedTemplateProperty; config.Resources.RegisterScriptModuleUrl("somemodule", "http://localhost:99999/somemodule.js", null); config.Markup.AddMarkupControl("cc", "CustomControl", "CustomControl.dotcontrol"); + config.Markup.AddMarkupControl("cc", "CustomControlWithStaticCommand", "CustomControlWithStaticCommand.dotcontrol"); config.Markup.AddMarkupControl("cc", "CustomControlWithCommand", "CustomControlWithCommand.dotcontrol"); config.Markup.AddMarkupControl("cc", "CustomControlWithProperty", "CustomControlWithProperty.dotcontrol"); config.Markup.AddMarkupControl("cc", "CustomControlWithInvalidVM", "CustomControlWithInvalidVM.dotcontrol"); @@ -68,14 +69,14 @@ public async Task MarkupControl_PassingStaticCommand() { var r = await cth.RunPage(typeof(BasicTestViewModel), @" - + - + ", directives: $"@service s = {typeof(TestService)}", markupFiles: new Dictionary { - ["CustomControlWithCommand.dotcontrol"] = @" + ["CustomControlWithStaticCommand.dotcontrol"] = @" @viewModel int @baseType DotVVM.Framework.Tests.ControlTests.CustomControlWithCommand @wrapperTag div @@ -86,6 +87,30 @@ @wrapperTag div check.CheckString(r.FormattedHtml, fileExtension: "html"); } + [TestMethod] + public async Task MarkupControl_CommandInRepeater() + { + var r = await cth.RunPage(typeof(BasicTestViewModel), @" + + + + + + ", + directives: $"@service s = {typeof(TestService)}", + markupFiles: new Dictionary { + ["CustomControlWithCommand.dotcontrol"] = @" + @viewModel int + @baseType DotVVM.Framework.Tests.ControlTests.CustomControlWithCommand + @wrapperTag div + " + } + ); + + check.CheckString(r.FormattedHtml, fileExtension: "html"); + } + + [TestMethod] public async Task MarkupControl_UpdateSource() { diff --git a/src/Tests/ControlTests/testoutputs/AutoUIFormTests.BootstrapFormTest.html b/src/Tests/ControlTests/testoutputs/AutoUIFormTests.BootstrapFormTest.html index d1ce0979b8..dee9128e52 100644 --- a/src/Tests/ControlTests/testoutputs/AutoUIFormTests.BootstrapFormTest.html +++ b/src/Tests/ControlTests/testoutputs/AutoUIFormTests.BootstrapFormTest.html @@ -55,7 +55,7 @@
  • diff --git a/src/Tests/ControlTests/testoutputs/AutoUIFormTests.BulmaFormTest.html b/src/Tests/ControlTests/testoutputs/AutoUIFormTests.BulmaFormTest.html index b8d04c80b1..f3baa8b0f7 100644 --- a/src/Tests/ControlTests/testoutputs/AutoUIFormTests.BulmaFormTest.html +++ b/src/Tests/ControlTests/testoutputs/AutoUIFormTests.BulmaFormTest.html @@ -86,7 +86,7 @@
    • diff --git a/src/Tests/ControlTests/testoutputs/AutoUITests.Selections.html b/src/Tests/ControlTests/testoutputs/AutoUITests.Selections.html index 87e42b4833..5312ed3ff8 100644 --- a/src/Tests/ControlTests/testoutputs/AutoUITests.Selections.html +++ b/src/Tests/ControlTests/testoutputs/AutoUITests.Selections.html @@ -29,7 +29,7 @@
      • diff --git a/src/Tests/ControlTests/testoutputs/MarkupControlTests.MarkupControl_CommandInRepeater.html b/src/Tests/ControlTests/testoutputs/MarkupControlTests.MarkupControl_CommandInRepeater.html new file mode 100644 index 0000000000..76be887516 --- /dev/null +++ b/src/Tests/ControlTests/testoutputs/MarkupControlTests.MarkupControl_CommandInRepeater.html @@ -0,0 +1,16 @@ + + + + + +
        + +
        + +
        +
        + +
        +
        + + diff --git a/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace.txt b/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace.txt index f2c8256b77..1178fad778 100644 --- a/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace.txt +++ b/src/Tests/Runtime/testoutputs/RuntimeErrorTests.CantFindDataContextSpace.txt @@ -1,2 +1,2 @@ -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=System.String, par=[Int32]), binding's context is (type=System.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's context is (type=string, par=[int]), binding's context is (type=string). Real data context types: . at object DotVVM.Framework.Controls.DotvvmBindableObject.EvalPropertyValue(DotvvmProperty property, object value) diff --git a/src/Tests/Runtime/testoutputs/RuntimeErrorTests.DataContextStack_ToString.txt b/src/Tests/Runtime/testoutputs/RuntimeErrorTests.DataContextStack_ToString.txt index eb327a571b..0be37fd5bb 100644 --- a/src/Tests/Runtime/testoutputs/RuntimeErrorTests.DataContextStack_ToString.txt +++ b/src/Tests/Runtime/testoutputs/RuntimeErrorTests.DataContextStack_ToString.txt @@ -1 +1 @@ -(type=System.Int32, imports=[import(System), import(Text=System.Text)], ext=[_index: Int32], par=[String, RuntimeErrorTests]) +(type=int, imports=[import(System), import(Text=System.Text)], ext=[_index: Int32], par=[string, RuntimeErrorTests])