Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Blazor] Allow null parameter values to be supplied to interactive components via enhanced page update #53317

Merged
merged 2 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 46 additions & 9 deletions src/Components/Endpoints/test/WebRootComponentParametersTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,36 +100,73 @@ public void WebRootComponentParameters_DefinitelyEquals_ReturnsTrue_ForEmptySetO
}

[Fact]
public void WebRootComponentParameters_DefinitelyEquals_Throws_WhenComparingNonJsonElementParameterToJsonElement()
public void WebRootComponentParameters_DefinitelyEquals_ReturnsFalse_WhenComparingNonJsonElementParameterToJsonElement()
{
// Arrange
var parameters1 = CreateParametersWithNonJsonElements(new() { ["First"] = 123 });
var parameters2 = CreateParameters(new() { ["First"] = 456 });

// Act/assert
Assert.Throws<InvalidCastException>(() => parameters1.DefinitelyEquals(parameters2));
// Act
var result = parameters1.DefinitelyEquals(parameters2);

// Assert
Assert.False(result);
}

[Fact]
public void WebRootComponentParameters_DefinitelyEquals_Throws_WhenComparingJsonElementParameterToNonJsonElement()
public void WebRootComponentParameters_DefinitelyEquals_ReturnsFalse_WhenComparingJsonElementParameterToNonJsonElement()
{
// Arrange
var parameters1 = CreateParameters(new() { ["First"] = 123 });
var parameters2 = CreateParametersWithNonJsonElements(new() { ["First"] = 456 });

// Act/assert
Assert.Throws<InvalidCastException>(() => parameters1.DefinitelyEquals(parameters2));
// Act
var result = parameters1.DefinitelyEquals(parameters2);

// Assert
Assert.False(result);
}

[Fact]
public void WebRootComponentParameters_DefinitelyEquals_ReturnsTrue_WhenComparingEqualNonJsonElementParameters()
{
// Arrange
var parameters1 = CreateParametersWithNonJsonElements(new() { ["First"] = 123 });
var parameters2 = CreateParametersWithNonJsonElements(new() { ["First"] = 123 });

// Act
var result = parameters1.DefinitelyEquals(parameters2);

// Assert
Assert.True(result);
}

[Fact]
public void WebRootComponentParameters_DefinitelyEquals_Throws_WhenComparingNonJsonElementParameters()
public void WebRootComponentParameters_DefinitelyEquals_ReturnsFalse_WhenComparingInequalNonJsonElementParameters()
{
// Arrange
var parameters1 = CreateParametersWithNonJsonElements(new() { ["First"] = 123 });
var parameters2 = CreateParametersWithNonJsonElements(new() { ["First"] = 456 });

// Act/assert
Assert.Throws<InvalidCastException>(() => parameters1.DefinitelyEquals(parameters2));
// Act
var result = parameters1.DefinitelyEquals(parameters2);

// Assert
Assert.False(result);
}

[Fact]
public void WebRootComponentParameters_DefinitelyEquals_ReturnsTrue_WhenComparingNullParameters()
{
// Arrange
var parameters1 = CreateParametersWithNonJsonElements(new() { ["First"] = null });
var parameters2 = CreateParametersWithNonJsonElements(new() { ["First"] = null });

// Act
var result = parameters1.DefinitelyEquals(parameters2);

// Assert
Assert.True(result);
}

private static WebRootComponentParameters CreateParameters(Dictionary<string, object> parameters)
Expand Down
14 changes: 11 additions & 3 deletions src/Components/Shared/src/WebRootComponentParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,17 @@ public bool DefinitelyEquals(in WebRootComponentParameters other)
return false;
}

var value = ((JsonElement)_serializedParameterValues[i]).GetRawText();
var otherValue = ((JsonElement)other._serializedParameterValues[i]).GetRawText();
if (!string.Equals(value, otherValue, StringComparison.Ordinal))
// We expect each serialized parameter value to be either a 'JsonElement' or 'null'.
var value = _serializedParameterValues[i];
var otherValue = other._serializedParameterValues[i];
if (value is JsonElement jsonValue && otherValue is JsonElement otherJsonValue)
{
if (!string.Equals(jsonValue.GetRawText(), otherJsonValue.GetRawText(), StringComparison.Ordinal))
{
return false;
}
}
else if (!Equals(value, otherValue))
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,36 @@ public void LocationChangingEventGetsInvokedOnEnhancedNavigationOnlyForRuntimeTh
Browser.Equal("0", () => Browser.Exists(By.Id($"location-changing-count-{anotherRuntime}")).Text);
}

[Theory]
[InlineData("server")]
[InlineData("wasm")]
public void CanReceiveNullParameterValueOnEnhancedNavigation(string renderMode)
{
// See: https://github.com/dotnet/aspnetcore/issues/52434
Navigate($"{ServerPathBase}/nav");
Browser.Equal("Hello", () => Browser.Exists(By.TagName("h1")).Text);

Browser.Exists(By.TagName("nav")).FindElement(By.LinkText($"Null component parameter ({renderMode})")).Click();
Browser.Equal("Page rendering component with null parameter", () => Browser.Exists(By.TagName("h1")).Text);
Browser.Equal("0", () => Browser.Exists(By.Id("current-count")).Text);

Browser.Exists(By.Id("button-increment")).Click();
Browser.Equal("1", () => Browser.Exists(By.Id("current-count")).Text);

// This refresh causes the interactive component to receive a 'null' parameter value
Browser.Exists(By.Id("button-refresh")).Click();
Browser.Equal("1", () => Browser.Exists(By.Id("current-count")).Text);
MackinnonBuck marked this conversation as resolved.
Show resolved Hide resolved

// Increment the count again to ensure that interactivity still works
Browser.Exists(By.Id("button-increment")).Click();
Browser.Equal("2", () => Browser.Exists(By.Id("current-count")).Text);

// Even if the interactive runtime continues to function (as the WebAssembly runtime might),
// fail the test if any errors were logged to the browser console
var logs = Browser.GetBrowserLogs(LogLevel.Warning);
Assert.DoesNotContain(logs, log => log.Message.Contains("Error"));
}

private void AssertEnhancedUpdateCountEquals(long count)
=> Browser.Equal(count, () => ((IJavaScriptExecutor)Browser).ExecuteScript("return window.enhancedPageUpdateCount;"));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@page "/nav/null-parameter/{mode}"
@using TestContentPackage

@* https://github.com/dotnet/aspnetcore/issues/52434 *@

<h1>Page rendering component with null parameter</h1>

@if (Mode == "server")
{
<ComponentAcceptingNullParameter @rendermode="RenderMode.InteractiveServer" Value="@null" />
}
else if (Mode == "wasm")
{
<ComponentAcceptingNullParameter @rendermode="RenderMode.InteractiveWebAssembly" Value="@null" />
}
else
{
<p>Expected a render mode of 'server' or 'wasm', but got '@Mode'.</p>
}

@code {
[Parameter]
public string? Mode { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
<NavLink href="nav/location-changed/server">LocationChanged/LocationChanging event (server)</NavLink>
<NavLink href="nav/location-changed/wasm">LocationChanged/LocationChanging event (wasm)</NavLink>
<NavLink href="nav/location-changed/server-and-wasm">LocationChanged/LocationChanging event (server-and-wasm)</NavLink>
<NavLink href="nav/null-parameter/server">Null component parameter (server)</NavLink>
<NavLink href="nav/null-parameter/wasm">Null component parameter (wasm)</NavLink>
<br />
<a href="nav/other" data-enhance-nav="false">
<svg width="100" height="100" id="svg-in-anchor-not-enhanced-nav-link">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
@inject NavigationManager NavigationManager

<p>Value: @(Value ?? "(null)")</p>

@if (_interactive)
{
<button id="button-increment" @onclick="Increment">Count: <span id="current-count">@_count</span></button>
<button id="button-refresh" @onclick="Refresh">Refresh</button>
}

@code {
private bool _interactive;
private int _count;

[Parameter]
public string Value { get; set; }

protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
_interactive = true;
StateHasChanged();
}
}

private void Increment()
{
_count++;
}

private void Refresh()
{
NavigationManager.Refresh();
}
}
Loading