diff --git a/src/Framework/Framework/Binding/BindingHelper.cs b/src/Framework/Framework/Binding/BindingHelper.cs index 46ac3becf..bc0c1c87c 100644 --- a/src/Framework/Framework/Binding/BindingHelper.cs +++ b/src/Framework/Framework/Binding/BindingHelper.cs @@ -101,7 +101,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 4eab7fd45..8420ee3d5 100644 --- a/src/Framework/Framework/Compilation/ControlTree/DataContextStack.cs +++ b/src/Framework/Framework/Compilation/ControlTree/DataContextStack.cs @@ -110,6 +110,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 2e1776e05..5dc77ac2e 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 ceac1d62e..dee7620f9 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"); @@ -71,14 +72,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 @@ -89,6 +90,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/HierarchyRepeaterTests.CommandInMarkupControl-client.html b/src/Tests/ControlTests/testoutputs/HierarchyRepeaterTests.CommandInMarkupControl-client.html index a70193353..045eb23e4 100644 --- a/src/Tests/ControlTests/testoutputs/HierarchyRepeaterTests.CommandInMarkupControl-client.html +++ b/src/Tests/ControlTests/testoutputs/HierarchyRepeaterTests.CommandInMarkupControl-client.html @@ -7,7 +7,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 000000000..76be88751 --- /dev/null +++ b/src/Tests/ControlTests/testoutputs/MarkupControlTests.MarkupControl_CommandInRepeater.html @@ -0,0 +1,16 @@ + + + + + +
+ +
+ +
+
+ +
+
+ +