From 1059afabc980ec425c995e6578a94ef8abcb06b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Standa=20Luke=C5=A1?= Date: Sat, 26 Oct 2024 22:55:36 +0200 Subject: [PATCH] postbacki handlers: Fix knockout context access in value bindings Fixes accesses to parent (or higher) knockout contexts or view models. It is necessary to call the AssignParameters function recursively on default assignments of the parent VM/context parameters. --- .../Javascript/ParametrizedCode.cs | 21 +++++-- .../Framework/Controls/KnockoutHelper.cs | 14 ++++- src/Tests/Binding/BindingCompilationTests.cs | 2 +- .../Binding/JavascriptCompilationTests.cs | 34 +++++++++++ .../ControlTests/PostbackHandlerTests.cs | 61 +++++++++++++++++++ .../PostbackHandlerTests.ButtonHandlers.html | 21 +++++++ ...ServerSideStyleTests.PostbackHandlers.html | 2 +- 7 files changed, 147 insertions(+), 8 deletions(-) create mode 100644 src/Tests/ControlTests/PostbackHandlerTests.cs create mode 100644 src/Tests/ControlTests/testoutputs/PostbackHandlerTests.ButtonHandlers.html diff --git a/src/Framework/Framework/Compilation/Javascript/ParametrizedCode.cs b/src/Framework/Framework/Compilation/Javascript/ParametrizedCode.cs index 95a0941035..fec592e561 100644 --- a/src/Framework/Framework/Compilation/Javascript/ParametrizedCode.cs +++ b/src/Framework/Framework/Compilation/Javascript/ParametrizedCode.cs @@ -141,20 +141,33 @@ public ParametrizedCode AssignParameters(Func - o == JavascriptTranslator.KnockoutContextParameter ? new ParametrizedCode("c") : - o == JavascriptTranslator.KnockoutViewModelParameter ? new ParametrizedCode("d") : + o == JavascriptTranslator.KnockoutContextParameter ? CodeParameterAssignment.FromIdentifier("c") : + o == JavascriptTranslator.KnockoutViewModelParameter ? CodeParameterAssignment.FromIdentifier("d") : default(CodeParameterAssignment) ); return new JsSymbolicParameter(new CodeSymbolicParameter("tmp symbol", defaultAssignment: adjustedCode)); + + // CodeParameterAssignment assignParameters(CodeSymbolicParameter parameter) => + // parameter switch { + // JavascriptTranslator.ViewModelSymbolicParameter { ParentIndex: 0 } => CodeParameterAssignment.FromIdentifier("c"), + // // _parent, _parent2 should be expanded to $context.$parent or similar to replace the $context variable + // JavascriptTranslator.ViewModelSymbolicParameter vm => vm.DefaultAssignment.Code!.AssignParameters(assignParameters), + // JavascriptTranslator.ContextSymbolicParameter { ParentIndex: 0 } => CodeParameterAssignment.FromIdentifier("d"), + // JavascriptTranslator.ContextSymbolicParameter ctx => ctx.DefaultAssignment.Code!.AssignParameters(assignParameters), + // _ => default + // }; } case IStaticValueBinding staticValueBinding: return new JsLiteral(staticValueBinding.Evaluate(handler)); diff --git a/src/Tests/Binding/BindingCompilationTests.cs b/src/Tests/Binding/BindingCompilationTests.cs index fdd0d6d360..1c7f2c6c8c 100755 --- a/src/Tests/Binding/BindingCompilationTests.cs +++ b/src/Tests/Binding/BindingCompilationTests.cs @@ -1493,7 +1493,7 @@ public override string ToString() return SomeString + ": " + MyProperty; } } - class TestViewModel3 : DotvvmViewModelBase + public class TestViewModel3 : DotvvmViewModelBase { public string SomeString { get; set; } } diff --git a/src/Tests/Binding/JavascriptCompilationTests.cs b/src/Tests/Binding/JavascriptCompilationTests.cs index d387558513..9575077737 100644 --- a/src/Tests/Binding/JavascriptCompilationTests.cs +++ b/src/Tests/Binding/JavascriptCompilationTests.cs @@ -1395,6 +1395,40 @@ public void JavascriptCompilation_CustomPrimitiveParse() Assert.AreEqual("VehicleNumber()==\"123\"", result); } + [TestMethod] + public void JavascriptCompilation_ParametrizedCode_ParentContexts() + { + // root: TestViewModel + // parent: TestViewModel2 + _index + _collection + // this: TestViewModel3 + _index + var context0 = DataContextStack.Create(typeof(TestViewModel)); + var context1 = DataContextStack.Create(typeof(TestViewModel2), parent: context0, extensionParameters: [ new CurrentCollectionIndexExtensionParameter(), new BindingCollectionInfoExtensionParameter("_collection") ]); + var context2 = DataContextStack.Create(typeof(TestViewModel3), parent: context1, extensionParameters: [ new CurrentCollectionIndexExtensionParameter() ]); + + var result = bindingHelper.ValueBindingToParametrizedCode("_root.IntProp + _parent._index + _parent._collection.Index + _parent.MyProperty + _index + SomeString.Length", context2); + + Assert.AreEqual("$parents[1].IntProp() + $parentContext.$index() + $parentContext.$index() + $parent.MyProperty() + $index() + SomeString().length", result.ToDefaultString()); + + // assign `context` and `vm` variables + var formatted2 = result.ToString(o => + o == JavascriptTranslator.KnockoutContextParameter ? new ParametrizedCode("context", OperatorPrecedence.Max) : + o == JavascriptTranslator.KnockoutViewModelParameter ? new ParametrizedCode("vm", OperatorPrecedence.Max) : + default(CodeParameterAssignment) + ); + Console.WriteLine(formatted2); + + var symbolicParameters = result.Parameters.ToArray(); + Assert.AreEqual(6, symbolicParameters.Length); + Assert.AreEqual(2, XAssert.IsAssignableFrom(symbolicParameters[0].Parameter).ParentIndex); + Assert.AreEqual(JavascriptTranslator.KnockoutContextParameter, symbolicParameters[1].Parameter); // JavascriptTranslator.ParentKnockoutContextParameter would also work + Assert.AreEqual(JavascriptTranslator.KnockoutContextParameter, symbolicParameters[2].Parameter); // JavascriptTranslator.ParentKnockoutContextParameter would also work + Assert.AreEqual(JavascriptTranslator.ParentKnockoutViewModelParameter, symbolicParameters[3].Parameter); + Assert.AreEqual(JavascriptTranslator.KnockoutContextParameter, symbolicParameters[4].Parameter); + Assert.AreEqual(JavascriptTranslator.KnockoutViewModelParameter, symbolicParameters[5].Parameter); + + Assert.AreEqual("context.$parents[1].IntProp() + context.$parentContext.$index() + context.$parentContext.$index() + context.$parent.MyProperty() + context.$index() + vm.SomeString().length", formatted2); + } + public class TestMarkupControl: DotvvmMarkupControl { public string SomeProperty diff --git a/src/Tests/ControlTests/PostbackHandlerTests.cs b/src/Tests/ControlTests/PostbackHandlerTests.cs new file mode 100644 index 0000000000..478587a7eb --- /dev/null +++ b/src/Tests/ControlTests/PostbackHandlerTests.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Threading.Tasks; +using CheckTestOutput; +using DotVVM.Framework.Compilation; +using DotVVM.Framework.Controls; +using DotVVM.Framework.Tests.Binding; +using DotVVM.Framework.ViewModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using DotVVM.Framework.Testing; +using System.Security.Claims; +using System.Collections; +using DotVVM.Framework.Binding; +using DotVVM.Framework.Compilation.ControlTree; +using DotVVM.Framework.Hosting; + +namespace DotVVM.Framework.Tests.ControlTests +{ + [TestClass] + public class PostbackHandlerTests + { + static readonly ControlTestHelper cth = new ControlTestHelper(config: config => { + }); + readonly OutputChecker check = new OutputChecker("testoutputs"); + + + [TestMethod] + public async Task ButtonHandlers() + { + var r = await cth.RunPage(typeof(BasicTestViewModel), """ + + + + 100 || SomeString.Length < 5} /> + + + + + + + + + + """ + ); + + check.CheckString(r.FormattedHtml, fileExtension: "html"); + } + + public class BasicTestViewModel: DotvvmViewModelBase + { + public int Integer { get; set; } = 123; + public bool Boolean { get; set; } = false; + public string String { get; set; } = "some-string"; + + public TestViewModel3 Nested { get; set; } = new TestViewModel3 { SomeString = "a" }; + } + } +} diff --git a/src/Tests/ControlTests/testoutputs/PostbackHandlerTests.ButtonHandlers.html b/src/Tests/ControlTests/testoutputs/PostbackHandlerTests.ButtonHandlers.html new file mode 100644 index 0000000000..08ca8d6fd0 --- /dev/null +++ b/src/Tests/ControlTests/testoutputs/PostbackHandlerTests.ButtonHandlers.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/src/Tests/ControlTests/testoutputs/ServerSideStyleTests.PostbackHandlers.html b/src/Tests/ControlTests/testoutputs/ServerSideStyleTests.PostbackHandlers.html index d2f57d3194..bc0ae66127 100644 --- a/src/Tests/ControlTests/testoutputs/ServerSideStyleTests.PostbackHandlers.html +++ b/src/Tests/ControlTests/testoutputs/ServerSideStyleTests.PostbackHandlers.html @@ -6,7 +6,7 @@ Two handlers Two handlers, value binding message - + Two handlers, resource binding message Two handlers, default message