diff --git a/Src/Dnn.Tests/ToSic.Sxc.Tests/ToSic.Sxc.Tests.csproj b/Src/Dnn.Tests/ToSic.Sxc.Tests/ToSic.Sxc.Tests.csproj
index 503c029a1b..703d1e2e7e 100644
--- a/Src/Dnn.Tests/ToSic.Sxc.Tests/ToSic.Sxc.Tests.csproj
+++ b/Src/Dnn.Tests/ToSic.Sxc.Tests/ToSic.Sxc.Tests.csproj
@@ -73,6 +73,8 @@
+
+
diff --git a/Src/Dnn.Tests/ToSic.Sxc.Tests/Web/CleanParamTests.cs b/Src/Dnn.Tests/ToSic.Sxc.Tests/Web/CleanParamTests.cs
new file mode 100644
index 0000000000..587a70e004
--- /dev/null
+++ b/Src/Dnn.Tests/ToSic.Sxc.Tests/Web/CleanParamTests.cs
@@ -0,0 +1,134 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using ToSic.Sxc.Web;
+
+namespace ToSic.Sxc.Tests.Web
+{
+ [TestClass]
+ public class CleanParamTests
+ {
+ [TestMethod]
+ public void FloatOrNull()
+ {
+ // Expected Nulls
+ Assert.AreEqual(null, CleanParam.FloatOrNull(null), "null");
+ Assert.AreEqual(null, CleanParam.FloatOrNull(new object()), "null");
+
+ // Expected Floats from other number formats
+ Assert.AreEqual(0f, CleanParam.FloatOrNull(0d), "double zero");
+ Assert.AreEqual(0f, CleanParam.FloatOrNull(0f), "float zero");
+ Assert.AreEqual(1.1f, CleanParam.FloatOrNull(1.1f), "float 1.1");
+ Assert.AreEqual(1.1f, CleanParam.FloatOrNull(1.1d), "double 1.1");
+
+ // Expected Floats
+ Assert.AreEqual(0f, CleanParam.FloatOrNull(0), "int zero");
+ Assert.AreEqual(0f, CleanParam.FloatOrNull("0"), "string zero");
+ Assert.AreEqual(0f, CleanParam.FloatOrNull("0.0"), "string 0.0");
+ Assert.AreEqual(7f, CleanParam.FloatOrNull(7), "int 7");
+ Assert.AreEqual(7f, CleanParam.FloatOrNull("7"), "string 7");
+ Assert.AreEqual(7.1f, CleanParam.FloatOrNull("7.1"), "string 7.1");
+ Assert.AreEqual(6.9f, CleanParam.FloatOrNull("6.9"), "string 6.9");
+ }
+
+ [TestMethod]
+ public void FloatOrNullWithCalculation()
+ {
+ // Check non-calculations
+ Assert.AreEqual(0, CleanParam.DoubleOrNullWithCalculation(0));
+ Assert.AreEqual(0, CleanParam.DoubleOrNullWithCalculation("0"));
+ Assert.AreEqual(2, CleanParam.DoubleOrNullWithCalculation("2"));
+ Assert.AreEqual(null, CleanParam.DoubleOrNullWithCalculation(""));
+
+
+ // Check calculations
+ Assert.AreEqual(1, CleanParam.DoubleOrNullWithCalculation("1/1"));
+ Assert.AreEqual(1, CleanParam.DoubleOrNullWithCalculation("1:1"));
+ Assert.AreEqual(0.5, CleanParam.DoubleOrNullWithCalculation("1/2"));
+ Assert.AreEqual(0.5, CleanParam.DoubleOrNullWithCalculation("1:2"));
+ Assert.AreEqual(2, CleanParam.DoubleOrNullWithCalculation("2/1"));
+ Assert.AreEqual(2, CleanParam.DoubleOrNullWithCalculation("2:1"));
+ Assert.AreEqual(16d / 9, CleanParam.DoubleOrNullWithCalculation("16:9"));
+ Assert.AreEqual(16d / 9, CleanParam.DoubleOrNullWithCalculation("16:9"));
+ Assert.AreEqual(16d / 9, CleanParam.DoubleOrNullWithCalculation("16:9"));
+
+ // Bad calculations
+ Assert.AreEqual(null, CleanParam.DoubleOrNullWithCalculation("/1"));
+ Assert.AreEqual(null, CleanParam.DoubleOrNullWithCalculation(":1"));
+ Assert.AreEqual(null, CleanParam.DoubleOrNullWithCalculation("1:0"));
+ Assert.AreEqual(null, CleanParam.DoubleOrNullWithCalculation("0:0"));
+ }
+
+ [TestMethod]
+ public void IntOrNull()
+ {
+ // Expected Nulls
+ Assert.AreEqual(null, CleanParam.IntOrNull(null), "null");
+ Assert.AreEqual(null, CleanParam.IntOrNull(new object()), "null");
+
+ // Expected Int
+ Assert.AreEqual(0, CleanParam.IntOrNull(0), "int zero");
+ Assert.AreEqual(0, CleanParam.IntOrNull(0f), "float zero");
+ Assert.AreEqual(0, CleanParam.IntOrNull(0d), "double zero");
+ Assert.AreEqual(0, CleanParam.IntOrNull("0"), "string zero");
+ Assert.AreEqual(0, CleanParam.IntOrNull("0.0"), "string 0.0");
+ Assert.AreEqual(7, CleanParam.IntOrNull(7), "int 7");
+ Assert.AreEqual(7, CleanParam.IntOrNull("7"), "string 7");
+ Assert.AreEqual(7, CleanParam.IntOrNull("7.1"), "string 7.1");
+ Assert.AreEqual(7, CleanParam.IntOrNull("6.9"), "string 6.9");
+ }
+
+
+ [TestMethod]
+ public void IntOrZeroNull()
+ {
+ // Expected Nulls
+ Assert.AreEqual(null, CleanParam.IntOrZeroAsNull(null), "null");
+ Assert.AreEqual(null, CleanParam.IntOrZeroAsNull(new object()), "null");
+
+ // Expected Zero Null Int
+ Assert.AreEqual(null, CleanParam.IntOrZeroAsNull(0), "int zero");
+ Assert.AreEqual(null, CleanParam.IntOrZeroAsNull("0"), "string zero");
+ Assert.AreEqual(null, CleanParam.IntOrZeroAsNull("0.0"), "string 0.0");
+ Assert.AreEqual(null, CleanParam.IntOrZeroAsNull(0f), "float zero");
+ Assert.AreEqual(null, CleanParam.IntOrZeroAsNull(0d), "double zero");
+
+ // Expected number
+ Assert.AreEqual(7, CleanParam.IntOrZeroAsNull(7), "int 7");
+ Assert.AreEqual(7, CleanParam.IntOrZeroAsNull("7"), "string 7");
+ Assert.AreEqual(7, CleanParam.IntOrZeroAsNull("7.1"), "string 7.1");
+ Assert.AreEqual(7, CleanParam.IntOrZeroAsNull("6.9"), "string 6.9");
+ }
+
+ [TestMethod]
+ public void RealStringOrNull()
+ {
+ // Expected Nulls
+ Assert.AreEqual(null, CleanParam.RealStringOrNull(null), "null");
+ Assert.AreEqual(null, CleanParam.RealStringOrNull(new object()), "null");
+
+ // Expected Zero Null Int
+ Assert.AreEqual("0", CleanParam.RealStringOrNull(0), "int zero");
+ Assert.AreEqual("0", CleanParam.RealStringOrNull("0"), "string zero");
+ Assert.AreEqual("0.0", CleanParam.RealStringOrNull("0.0"), "string 0.0");
+ Assert.AreEqual("0", CleanParam.RealStringOrNull(0f), "float zero");
+ Assert.AreEqual("0", CleanParam.RealStringOrNull(0d), "double zero");
+
+ // Expected number
+ Assert.AreEqual("7", CleanParam.RealStringOrNull(7), "int 7");
+ Assert.AreEqual("7", CleanParam.RealStringOrNull("7"), "string 7");
+ Assert.AreEqual("7.1", CleanParam.RealStringOrNull("7.1"), "string 7.1");
+ Assert.AreEqual("6.9", CleanParam.RealStringOrNull("6.9"), "string 6.9");
+ }
+
+ [TestMethod]
+ public void FNearZero()
+ {
+ Assert.IsTrue(CleanParam.FNearZero(0));
+ Assert.IsTrue(CleanParam.FNearZero(0.0001f));
+ Assert.IsTrue(CleanParam.FNearZero(-0.009f));
+ Assert.IsFalse(CleanParam.FNearZero(0.2f));
+ Assert.IsFalse(CleanParam.FNearZero(2f));
+ }
+
+ }
+}
diff --git a/Src/Dnn.Tests/ToSic.Sxc.Tests/Web/LinkImageBasicTests.cs b/Src/Dnn.Tests/ToSic.Sxc.Tests/Web/LinkImageBasicTests.cs
index 920a2f40a1..598c2c35d4 100644
--- a/Src/Dnn.Tests/ToSic.Sxc.Tests/Web/LinkImageBasicTests.cs
+++ b/Src/Dnn.Tests/ToSic.Sxc.Tests/Web/LinkImageBasicTests.cs
@@ -42,8 +42,45 @@ public void BasicWidthAndAspectRatio()
Assert.AreEqual("test.jpg?w=200", linker.Image("test.jpg", width: 200, aspectRatio: 0));
Assert.AreEqual("test.jpg?w=200&h=200", linker.Image("test.jpg", width: 200, aspectRatio: 1));
- Assert.AreEqual("test.jpg?w=200&h=400", linker.Image("test.jpg", width: 200, aspectRatio: 2));
- Assert.AreEqual("test.jpg?w=200&h=100", linker.Image("test.jpg", width: 200, aspectRatio: 0.5));
+ Assert.AreEqual("test.jpg?w=200&h=400", linker.Image("test.jpg", width: 200, aspectRatio: 0.5));
+ Assert.AreEqual("test.jpg?w=200&h=100", linker.Image("test.jpg", width: 200, aspectRatio: 2));
+ Assert.AreEqual("test.jpg?w=200&h=80", linker.Image("test.jpg", width: 200, aspectRatio: 2.5));
+
+ // Note: in this case it should be 112.5 and will be rounded down by default
+ Assert.AreEqual("test.jpg?w=200&h=112", linker.Image("test.jpg", width: 200, aspectRatio: 16f/9));
+ }
+
+ [TestMethod]
+ public void BasicWidthAndAspectRatioString()
+ {
+ var linker = new ImgResizeLinker();
+
+ // Simple Strings
+ Assert.AreEqual("test.jpg?w=200", linker.Image("test.jpg", width: 200, aspectRatio: "0"));
+ Assert.AreEqual("test.jpg?w=200&h=200", linker.Image("test.jpg", width: 200, aspectRatio: "1"));
+ Assert.AreEqual("test.jpg?w=200&h=400", linker.Image("test.jpg", width: 200, aspectRatio: "0.5"));
+ Assert.AreEqual("test.jpg?w=200&h=100", linker.Image("test.jpg", width: 200, aspectRatio: "2"));
+ Assert.AreEqual("test.jpg?w=200&h=80", linker.Image("test.jpg", width: 200, aspectRatio: "2.5"));
+ }
+
+ [TestMethod]
+ public void BasicWidthAndAspectRatioStringWithSeparator()
+ {
+ var linker = new ImgResizeLinker();
+
+ // Simple Strings
+ Assert.AreEqual("test.jpg?w=200", linker.Image("test.jpg", width: 200, aspectRatio: "0"));
+ Assert.AreEqual("test.jpg?w=200&h=200", linker.Image("test.jpg", width: 200, aspectRatio: "1:1"));
+ Assert.AreEqual("test.jpg?w=200&h=200", linker.Image("test.jpg", width: 200, aspectRatio: "1/1"));
+ Assert.AreEqual("test.jpg?w=200&h=400", linker.Image("test.jpg", width: 200, aspectRatio: "1:2"));
+ Assert.AreEqual("test.jpg?w=200&h=400", linker.Image("test.jpg", width: 200, aspectRatio: "1/2"));
+ Assert.AreEqual("test.jpg?w=200&h=100", linker.Image("test.jpg", width: 200, aspectRatio: "2:1"));
+ Assert.AreEqual("test.jpg?w=200&h=100", linker.Image("test.jpg", width: 200, aspectRatio: "2/1"));
+ Assert.AreEqual("test.jpg?w=200&h=100", linker.Image("test.jpg", width: 200, aspectRatio: "2"));
+ Assert.AreEqual("test.jpg?w=200&h=80", linker.Image("test.jpg", width: 200, aspectRatio: "2.5"));
+
+ // Note: in this case it should be 112.5 and will be rounded down by default
+ Assert.AreEqual("test.jpg?w=200&h=112", linker.Image("test.jpg", width: 200, aspectRatio: "16/9"));
}
[TestMethod]
diff --git a/Src/Dnn.Tests/ToSic.Sxc.Tests/Web/LinkImageInternalsTests.cs b/Src/Dnn.Tests/ToSic.Sxc.Tests/Web/LinkImageInternalsTests.cs
new file mode 100644
index 0000000000..093c3cb42f
--- /dev/null
+++ b/Src/Dnn.Tests/ToSic.Sxc.Tests/Web/LinkImageInternalsTests.cs
@@ -0,0 +1,100 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using ToSic.Sxc.Data;
+using ToSic.Sxc.Web;
+
+namespace ToSic.Sxc.Tests.Web
+{
+ [TestClass]
+ public class LinkImageInternalsTests
+ {
+
+
+ const int s = 1440; // standard value, divisible by 1/2/3/4/6/12
+
+ [TestMethod]
+ public void FigureOutBestWidthAndHeight()
+ {
+ // All Zero and Nulls
+ Assert.IsTrue(TestBestWH(0, 0), "Null everything");
+ Assert.IsTrue(TestBestWH(0, 0, 0), "Null everything");
+ Assert.IsTrue(TestBestWH(0, 0, 0, 0), "Null everything");
+ Assert.IsTrue(TestBestWH(0, 0, 0, 0, 0), "Null everything");
+ Assert.IsTrue(TestBestWH(0, 0, 0, 0, 0, 0), "Null everything");
+
+ // Single Aspect is set
+ Assert.IsTrue(TestBestWH(s, 0, width: s), "W only");
+ Assert.IsTrue(TestBestWH(0, s, height: s), "H only");
+ Assert.IsTrue(TestBestWH(0, 0, factor: 7), "factor only");
+ Assert.IsTrue(TestBestWH(0, 0, ar: 7), "ar only");
+
+ // H / W
+ Assert.IsTrue(TestBestWH(s, s, width: s, height: s), "W & H only");
+ }
+
+ [TestMethod]
+ public void FigureOutBestWidthAndHeight_Factors()
+ {
+ // W / Factor - factor shouldn't apply
+ Assert.IsTrue(TestBestWH(s, 0, width: s, factor: 1), "W/F only");
+ Assert.IsTrue(TestBestWH(s, 0, width: s, factor: .5), "W/F only");
+ Assert.IsTrue(TestBestWH(s, 0, width: s, factor: 2), "W/F only");
+ Assert.IsTrue(TestBestWH(s, 0, width: s, factor: 1.5), "W/F only");
+
+ // H / Factor - factor shouldn't apply
+ Assert.IsTrue(TestBestWH(0, s, height: s, factor: 1), "H/F only");
+ Assert.IsTrue(TestBestWH(0, s, height: s, factor: .5), "H/F only");
+ Assert.IsTrue(TestBestWH(0, s, height: s, factor: 2), "H/F only");
+ Assert.IsTrue(TestBestWH(0, s, height: s, factor: 1.5), "H/F only");
+
+ // H / W / Factor - factor shouldn't apply
+ Assert.IsTrue(TestBestWH(s, s, width: s, height: s, factor: 1), "W/H/F");
+ Assert.IsTrue(TestBestWH(s, s, width: s, height: s, factor: .5), "W/H/F");
+ Assert.IsTrue(TestBestWH(s, s, width: s, height: s, factor: 2), "W/H/F");
+ Assert.IsTrue(TestBestWH(s, s, width: s, height: s, factor: 1.5), "W/H/F");
+ }
+
+ [TestMethod]
+ public void FigureOutBestWidthAndHeight_SettingsWH()
+ {
+ Assert.IsTrue(TestBestWH(s, 0, settings: ToDyn(new { Width = s })), "W only");
+ Assert.IsTrue(TestBestWH(0, s, settings: ToDyn(new { Height = s })), "H only");
+ Assert.IsTrue(TestBestWH(0, 0, settings: ToDyn(new { })), "No params");
+ Assert.IsTrue(TestBestWH(s, s, settings: ToDyn(new { Width = s, Height = s })), "W/H params");
+ }
+
+ [TestMethod]
+ public void FigureOutBestWidthAndHeight_SettingsWHF()
+ {
+ FigureOutWHFactors(0, 1);
+ FigureOutWHFactors(1, 1);
+ FigureOutWHFactors(2, 2);
+ FigureOutWHFactors(0.5, 0.5);
+ }
+
+ private void FigureOutWHFactors(double factor, double fResult)
+ {
+ Console.WriteLine("Run with Factor: " + factor);
+ // Factor 1
+ Assert.IsTrue(TestBestWH((int)(fResult * s), 0, factor: factor, settings: ToDyn(new { Width = s })), $"W with f:{factor}");
+ Assert.IsTrue(TestBestWH(0, (int)(fResult * s), factor: factor, settings: ToDyn(new { Height = s })), $"H with f:{factor}");
+ Assert.IsTrue(TestBestWH(0, 0, factor: factor, settings: ToDyn(new { })), $"No params f:{factor}");
+ Assert.IsTrue(TestBestWH((int)(fResult * s), (int)(fResult * s), factor: factor, settings: ToDyn(new { Width = s, Height = s })), $"W/H f:{factor}");
+ }
+
+ private DynamicReadObject ToDyn(object o) => new DynamicReadObject(o);
+
+
+ private bool TestBestWH(int expW, int expH, object width = null, object height = null, object factor = null, object ar = null, ICanGetNameNotFinal settings = null)
+ {
+ var linker = new ImgResizeLinker();
+
+ var t1 = linker.FigureOutBestWidthAndHeight(width, height, factor, ar, settings);
+ var ok = Equals(new Tuple(expW, expH), t1);
+ var okW = expW.Equals(t1.Item1);
+ var okH = expH.Equals(t1.Item2);
+ Console.WriteLine((ok ? "ok" : "error") + "; W:" + t1.Item1 + (okW ? "==": "!=") + expW + "; H:" + t1.Item2 + (okH ? "==" : "!=") + expH);
+ return ok;
+ }
+ }
+}
diff --git a/Src/Sxc/ToSic.Sxc/Data/DynamicWrapper/DynamicReadObject.cs b/Src/Sxc/ToSic.Sxc/Data/DynamicWrapper/DynamicReadObject.cs
index 516a6b3d47..6a513b77ef 100644
--- a/Src/Sxc/ToSic.Sxc/Data/DynamicWrapper/DynamicReadObject.cs
+++ b/Src/Sxc/ToSic.Sxc/Data/DynamicWrapper/DynamicReadObject.cs
@@ -30,7 +30,7 @@ public partial class DynamicReadObject: DynamicObject, IWrapper