();
+ testCasePicker = new SelectElement(app.FindElement(By.Id("dynamic-component-case-picker")));
+ }
+
+ [Fact]
+ public void CanRenderComponentDynamically()
+ {
+ var hostRenderCountDisplay = app.FindElement(By.Id("outer-rendercount"));
+ Browser.Equal("1", () => hostRenderCountDisplay.Text);
+
+ testCasePicker.SelectByText("Counter");
+ Browser.Equal("2", () => hostRenderCountDisplay.Text);
+
+ // Basic rendering of a dynamic child works
+ var childContainer = app.FindElement(By.Id("dynamic-child"));
+ var currentCountDisplay = childContainer.FindElements(By.TagName("p")).First();
+ Browser.Equal("Current count: 0", () => currentCountDisplay.Text);
+
+ // The dynamic child can process events and re-render as normal
+ var incrementButton = childContainer.FindElement(By.TagName("button"));
+ incrementButton.Click();
+ Browser.Equal("Current count: 1", () => currentCountDisplay.Text);
+
+ // Re-rendering the child doesn't re-render the host
+ Browser.Equal("2", () => hostRenderCountDisplay.Text);
+
+ // Re-rendering the host doesn't lose state in the child (e.g., by recreating it)
+ app.FindElement(By.Id("re-render-host")).Click();
+ Browser.Equal("3", () => hostRenderCountDisplay.Text);
+ Browser.Equal("Current count: 1", () => currentCountDisplay.Text);
+ incrementButton.Click();
+ Browser.Equal("Current count: 2", () => currentCountDisplay.Text);
+ }
+
+ [Fact]
+ public void CanPassParameters()
+ {
+ testCasePicker.SelectByText("Component with parameters");
+ var dynamicChild = app.FindElement(By.Id("dynamic-child"));
+
+ // Regular parameters work
+ Browser.Equal("Hello 123", () => dynamicChild.FindElement(By.CssSelector(".Param1 li")).Text);
+
+ // Derived parameters work
+ Browser.Equal("Goodbye Derived", () => dynamicChild.FindElement(By.CssSelector(".Param2")).Text);
+
+ // Catch-all parameters work
+ Browser.Equal("unmatchedParam This is the unmatched param value", () => dynamicChild.FindElement(By.CssSelector(".Param3 li")).Text);
+ }
+
+ [Fact]
+ public void CanChangeDynamicallyRenderedComponent()
+ {
+ testCasePicker.SelectByText("Component with parameters");
+ var dynamicChild = app.FindElement(By.Id("dynamic-child"));
+ Browser.Equal("Component With Parameters", () => dynamicChild.FindElement(By.TagName("h3")).Text);
+
+ testCasePicker.SelectByText("Counter");
+ Browser.Equal("Counter", () => dynamicChild.FindElement(By.TagName("h1")).Text);
+ }
+ }
+}
diff --git a/src/Components/test/testassets/BasicTestApp/DynamicComponentRendering.razor b/src/Components/test/testassets/BasicTestApp/DynamicComponentRendering.razor
new file mode 100644
index 000000000000..21ebc241fe19
--- /dev/null
+++ b/src/Components/test/testassets/BasicTestApp/DynamicComponentRendering.razor
@@ -0,0 +1,80 @@
+DynamicComponent
+
+
+ Select case:
+
+
+
+
+ Outer component render count: @(++renderCount)
+
+
+
+
+
+@if (!string.IsNullOrEmpty(currentCaseName))
+{
+ var currentCase = cases[currentCaseName];
+
+
+
+}
+
+@code {
+ private int renderCount;
+ private string currentCaseName;
+
+ private Dictionary cases = new Dictionary
+ {
+ {
+ "Counter",
+ new TestCase { ComponentType = typeof(CounterComponent) }
+ },
+ {
+ "Component with parameters",
+ new TestCase
+ {
+ ComponentType = typeof(ComponentWithParameters),
+ ComponentParameters = new Dictionary
+ {
+ // Known parameters
+ {
+ nameof(ComponentWithParameters.Param1),
+ new List
+ {
+ new ComponentWithParameters.TestModel { IntProperty = 123, StringProperty = "Hello" }
+ }
+ },
+ {
+ nameof(ComponentWithParameters.Param2),
+ new ComponentWithParameters.DerivedModel { IntProperty = 456, StringProperty = "Goodbye", DerivedProperty = "Derived" }
+ },
+
+ // An unmatched parameter
+ {
+ "unmatchedParam",
+ "This is the unmatched param value"
+ }
+ }
+ }
+ },
+ };
+
+ void NoOpEventHandler()
+ {
+ // The purpose of this is just to make the host component re-render so the test
+ // can verify that no state was lost
+ }
+
+ class TestCase
+ {
+ public Type ComponentType { get; set; }
+ public Dictionary ComponentParameters { get; set; }
+ }
+}
diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor
index e6b8138a7dbd..b1d97155db54 100644
--- a/src/Components/test/testassets/BasicTestApp/Index.razor
+++ b/src/Components/test/testassets/BasicTestApp/Index.razor
@@ -21,6 +21,7 @@
+