From 3ee23441e770b1a9eb8477a72513a06f341175d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Standa=20Luke=C5=A1?= Date: Sun, 11 Sep 2022 22:00:34 +0200 Subject: [PATCH] data context matching based on types: fixed few problems --- .../Framework/Binding/BindingHelper.cs | 27 ++++++++++++++-- .../ControlTree/DataContextStack.cs | 17 +++++++++- .../Tests/Tests/Complex/TaskListTests.cs | 2 +- src/Tests/ControlTests/MarkupControlTests.cs | 31 +++++++++++++++++-- .../AutoUIFormTests.BootstrapFormTest.html | 2 +- .../AutoUIFormTests.BulmaFormTest.html | 2 +- .../testoutputs/AutoUITests.Selections.html | 2 +- ...Tests.MarkupControl_CommandInRepeater.html | 16 ++++++++++ 8 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 src/Tests/ControlTests/testoutputs/MarkupControlTests.MarkupControl_CommandInRepeater.html diff --git a/src/Framework/Framework/Binding/BindingHelper.cs b/src/Framework/Framework/Binding/BindingHelper.cs index b69e947c37..ccfdb93f50 100644 --- a/src/Framework/Framework/Binding/BindingHelper.cs +++ b/src/Framework/Framework/Binding/BindingHelper.cs @@ -97,8 +97,31 @@ 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++; - lastAncestorContext = ancestorContext; + + // don't count changes which only extend the data context, but don't nest it + + var isNesting = ancestorContext.IsAncestorOf(lastAncestorContext); + if (isNesting) + { + changes++; + lastAncestorContext = ancestorContext; + } +#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.DataContextType.ToCode(stripNamespace: true)}, " + + $"{a.DebugString(config)} has DataContext: {ancestorContext.DataContextType.ToCode(stripNamespace: true)}"); + } +#endif } if (bindingContext.Equals(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 @@ + + + + + +
        + +
        + +
        +
        + +
        +
        + +