Skip to content

Commit

Permalink
Forms behavior clarifications (#49340)
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveSandersonMS authored Jul 17, 2023
1 parent d24da32 commit e5bb36d
Show file tree
Hide file tree
Showing 77 changed files with 1,226 additions and 958 deletions.
9 changes: 5 additions & 4 deletions src/Components/Components/src/CascadingParameterState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Reflection;
using Microsoft.AspNetCore.Components.Reflection;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.RenderTree;
using static Microsoft.AspNetCore.Internal.LinkerFlags;

namespace Microsoft.AspNetCore.Components;
Expand Down Expand Up @@ -43,7 +44,7 @@ public static IReadOnlyList<CascadingParameterState> FindCascadingParameters(Com
for (var infoIndex = 0; infoIndex < numInfos; infoIndex++)
{
ref var info = ref infos[infoIndex];
var supplier = GetMatchingCascadingValueSupplier(info, componentState);
var supplier = GetMatchingCascadingValueSupplier(info, componentState.Renderer, componentState.LogicalParentComponentState);
if (supplier != null)
{
// Although not all parameters might be matched, we know the maximum number
Expand All @@ -55,10 +56,10 @@ public static IReadOnlyList<CascadingParameterState> FindCascadingParameters(Com
return resultStates ?? (IReadOnlyList<CascadingParameterState>)Array.Empty<CascadingParameterState>();
}

private static ICascadingValueSupplier? GetMatchingCascadingValueSupplier(in CascadingParameterInfo info, ComponentState componentState)
internal static ICascadingValueSupplier? GetMatchingCascadingValueSupplier(in CascadingParameterInfo info, Renderer renderer, ComponentState? componentState)
{
// First scan up through the component hierarchy
var candidate = componentState.LogicalParentComponentState;
var candidate = componentState;
while (candidate is not null)
{
if (candidate.Component is ICascadingValueSupplier valueSupplier && valueSupplier.CanSupplyValue(info))
Expand All @@ -70,7 +71,7 @@ public static IReadOnlyList<CascadingParameterState> FindCascadingParameters(Com
}

// We got to the root and found no match, so now look at the providers registered in DI
foreach (var valueSupplier in componentState.Renderer.ServiceProviderCascadingValueSuppliers)
foreach (var valueSupplier in renderer.ServiceProviderCascadingValueSuppliers)
{
if (valueSupplier.CanSupplyValue(info))
{
Expand Down
21 changes: 12 additions & 9 deletions src/Components/Components/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,18 @@ Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentRenderMo
Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddNamedEvent(int sequence, string! eventType, string! assignedName) -> void
Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags.HasCallerSpecifiedRenderMode = 1 -> Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
Microsoft.AspNetCore.Components.RenderTree.NamedEvent
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.AssignedName.get -> string!
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.ComponentId.get -> int
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.EventType.get -> string!
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.FrameIndex.get -> int
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.NamedEvent() -> void
Microsoft.AspNetCore.Components.RenderTree.NamedEvent.NamedEvent(int componentId, int frameIndex, string! eventType, string! assignedName) -> void
Microsoft.AspNetCore.Components.RenderTree.RenderBatch.AddedNamedEvents.get -> Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.NamedEvent>?
Microsoft.AspNetCore.Components.RenderTree.RenderBatch.RemovedNamedEvents.get -> Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.NamedEvent>?
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange.AssignedName.get -> string!
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange.ChangeType.get -> Microsoft.AspNetCore.Components.RenderTree.NamedEventChangeType
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange.ComponentId.get -> int
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange.EventType.get -> string!
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange.FrameIndex.get -> int
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange.NamedEventChange() -> void
Microsoft.AspNetCore.Components.RenderTree.NamedEventChange.NamedEventChange(Microsoft.AspNetCore.Components.RenderTree.NamedEventChangeType changeType, int componentId, int frameIndex, string! eventType, string! assignedName) -> void
Microsoft.AspNetCore.Components.RenderTree.NamedEventChangeType
Microsoft.AspNetCore.Components.RenderTree.NamedEventChangeType.Added = 0 -> Microsoft.AspNetCore.Components.RenderTree.NamedEventChangeType
Microsoft.AspNetCore.Components.RenderTree.NamedEventChangeType.Removed = 1 -> Microsoft.AspNetCore.Components.RenderTree.NamedEventChangeType
Microsoft.AspNetCore.Components.RenderTree.RenderBatch.NamedEventChanges.get -> Microsoft.AspNetCore.Components.RenderTree.ArrayRange<Microsoft.AspNetCore.Components.RenderTree.NamedEventChange>?
Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrame.ComponentFrameFlags.get -> Microsoft.AspNetCore.Components.RenderTree.ComponentFrameFlags
Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType.ComponentRenderMode = 9 -> Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType
Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType.NamedEvent = 10 -> Microsoft.AspNetCore.Components.RenderTree.RenderTreeFrameType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,20 @@ namespace Microsoft.AspNetCore.Components.RenderTree;
/// of the Blazor framework. These types will change in a future release.
/// </summary>
/// <remarks>
/// Constructs an instance of <see cref="NamedEvent"/>.
/// Constructs an instance of <see cref="NamedEventChange"/>.
/// </remarks>
/// <param name="changeType">The type of the change.</param>
/// <param name="componentId">The ID of the component holding the named value.</param>
/// <param name="frameIndex">The index of the <see cref="RenderTreeFrameType.NamedEvent"/> frame within the component's current render output.</param>
/// <param name="eventType">The event type.</param>
/// <param name="assignedName">The application-assigned name.</param>
public readonly struct NamedEvent(int componentId, int frameIndex, string eventType, string assignedName)
public readonly struct NamedEventChange(NamedEventChangeType changeType, int componentId, int frameIndex, string eventType, string assignedName)
{
/// <summary>
/// Describes the type of the change.
/// </summary>
public readonly NamedEventChangeType ChangeType { get; } = changeType;

/// <summary>
/// The ID of the component holding the named event.
/// </summary>
Expand Down
20 changes: 20 additions & 0 deletions src/Components/Components/src/RenderTree/NamedEventChangeType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.Components.RenderTree;

/// <summary>
/// Describes a change to a named event.
/// </summary>
public enum NamedEventChangeType : int
{
/// <summary>
/// Indicates that the item was added.
/// </summary>
Added,

/// <summary>
/// Indicates that the item was removed.
/// </summary>
Removed,
}
15 changes: 4 additions & 11 deletions src/Components/Components/src/RenderTree/RenderBatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,29 +34,22 @@ public readonly struct RenderBatch
public ArrayRange<ulong> DisposedEventHandlerIDs { get; }

/// <summary>
/// Gets the named events that were added, or null.
/// Gets the named events that were changed, or null.
/// </summary>
public ArrayRange<NamedEvent>? AddedNamedEvents { get; }

/// <summary>
/// Gets the named events that were removed, or null.
/// </summary>
public ArrayRange<NamedEvent>? RemovedNamedEvents { get; }
public ArrayRange<NamedEventChange>? NamedEventChanges { get; }

internal RenderBatch(
ArrayRange<RenderTreeDiff> updatedComponents,
ArrayRange<RenderTreeFrame> referenceFrames,
ArrayRange<int> disposedComponentIDs,
ArrayRange<ulong> disposedEventHandlerIDs,
ArrayRange<NamedEvent>? addedNamedEvents,
ArrayRange<NamedEvent>? removedNamedEvents)
ArrayRange<NamedEventChange>? changedNamedEvents)
{
UpdatedComponents = updatedComponents;
ReferenceFrames = referenceFrames;
DisposedComponentIDs = disposedComponentIDs;
DisposedEventHandlerIDs = disposedEventHandlerIDs;
AddedNamedEvents = addedNamedEvents;
RemovedNamedEvents = removedNamedEvents;
NamedEventChanges = changedNamedEvents;
}
}

20 changes: 8 additions & 12 deletions src/Components/Components/src/Rendering/RenderBatchBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ internal sealed class RenderBatchBuilder : IDisposable
public ArrayBuilder<RenderTreeDiff> UpdatedComponentDiffs { get; } = new ArrayBuilder<RenderTreeDiff>();
public ArrayBuilder<int> DisposedComponentIds { get; } = new ArrayBuilder<int>();
public ArrayBuilder<ulong> DisposedEventHandlerIds { get; } = new ArrayBuilder<ulong>();
public ArrayBuilder<NamedEvent>? AddedNamedEvents;
public ArrayBuilder<NamedEvent>? RemovedNamedEvents;
public ArrayBuilder<NamedEventChange>? NamedEventChanges;

// Buffers referenced by UpdatedComponentDiffs
public ArrayBuilder<RenderTreeEdit> EditsBuffer { get; } = new ArrayBuilder<RenderTreeEdit>(64);
Expand Down Expand Up @@ -56,8 +55,7 @@ public void ClearStateForCurrentBatch()
DisposedComponentIds.Clear();
DisposedEventHandlerIds.Clear();
AttributeDiffSet.Clear();
AddedNamedEvents?.Clear();
RemovedNamedEvents?.Clear();
NamedEventChanges?.Clear();
}

public RenderBatch ToBatch()
Expand All @@ -66,8 +64,7 @@ public RenderBatch ToBatch()
ReferenceFramesBuffer.ToRange(),
DisposedComponentIds.ToRange(),
DisposedEventHandlerIds.ToRange(),
AddedNamedEvents?.ToRange(),
RemovedNamedEvents?.ToRange());
NamedEventChanges?.ToRange());

public void InvalidateParameterViews()
{
Expand All @@ -87,14 +84,14 @@ public void InvalidateParameterViews()

public void AddNamedEvent(int componentId, int frameIndex, ref RenderTreeFrame frame)
{
AddedNamedEvents ??= new();
AddedNamedEvents.Append(new NamedEvent(componentId, frameIndex, frame.NamedEventType, frame.NamedEventAssignedName));
NamedEventChanges ??= new();
NamedEventChanges.Append(new NamedEventChange(NamedEventChangeType.Added, componentId, frameIndex, frame.NamedEventType, frame.NamedEventAssignedName));
}

public void RemoveNamedEvent(int componentId, int frameIndex, ref RenderTreeFrame frame)
{
RemovedNamedEvents ??= new();
RemovedNamedEvents.Append(new NamedEvent(componentId, frameIndex, frame.NamedEventType, frame.NamedEventAssignedName));
NamedEventChanges ??= new();
NamedEventChanges.Append(new NamedEventChange(NamedEventChangeType.Removed, componentId, frameIndex, frame.NamedEventType, frame.NamedEventAssignedName));
}

public void Dispose()
Expand All @@ -104,7 +101,6 @@ public void Dispose()
UpdatedComponentDiffs.Dispose();
DisposedComponentIds.Dispose();
DisposedEventHandlerIds.Dispose();
AddedNamedEvents?.Dispose();
RemovedNamedEvents?.Dispose();
NamedEventChanges?.Dispose();
}
}
12 changes: 6 additions & 6 deletions src/Components/Components/src/Rendering/RenderTreeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public sealed class RenderTreeBuilder : IDisposable
private bool _hasSeenAddMultipleAttributes;
private Dictionary<string, int>? _seenAttributeNames;
private IComponentRenderMode? _pendingComponentCallSiteRenderMode; // TODO: Remove when Razor compiler supports call-site @rendermode
private (int Sequence, string AssignedName)? _pendingNamedSubmitEvent; // TODO: Remove when Razor compiler supports @onsubmit:name
private (int Sequence, string AssignedName)? _pendingNamedSubmitEvent; // TODO: Remove when Razor compiler supports @formname

/// <summary>
/// The reserved parameter name used for supplying child content.
Expand Down Expand Up @@ -80,7 +80,7 @@ public void CloseElement()
_entries.Buffer[indexOfEntryBeingClosed].ElementSubtreeLengthField = _entries.Count - indexOfEntryBeingClosed;
}

// TODO: Remove this once Razor supports @onsubmit:name
// TODO: Remove this once Razor supports @formname
private void CompletePendingNamedSubmitEvent()
{
if (_pendingNamedSubmitEvent is { } pendingNamedSubmitEvent)
Expand Down Expand Up @@ -237,9 +237,9 @@ public void AddAttribute(int sequence, string name, string? value)
AssertCanAddAttribute();
if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component)
{
// TODO: Remove this once the Razor compiler is updated to support @onsubmit:name
// That should compile directly as a call to AddNamedValue.
if (string.Equals(name, "@onsubmit:name", StringComparison.Ordinal) && _lastNonAttributeFrameType == RenderTreeFrameType.Element)
// TODO: Remove this once the Razor compiler is updated to support @formname
// That should compile directly as a call to AddNamedEvent.
if (string.Equals(name, "@formname", StringComparison.Ordinal) && _lastNonAttributeFrameType == RenderTreeFrameType.Element)
{
_pendingNamedSubmitEvent = (sequence, value!);
}
Expand Down Expand Up @@ -722,7 +722,7 @@ public void AddComponentRenderMode(int sequence, IComponentRenderMode renderMode
public void AddNamedEvent(int sequence, string eventType, string assignedName)
{
ArgumentNullException.ThrowIfNull(eventType);
ArgumentNullException.ThrowIfNull(assignedName);
ArgumentException.ThrowIfNullOrEmpty(assignedName);

// Note that we could trivially extend this to a generic concept of "named values" that exist within the rendertree
// and are tracked when added, removed, or updated. Currently we don't need that generality, but if we ever do, we
Expand Down
34 changes: 16 additions & 18 deletions src/Components/Components/test/RenderTreeDiffBuilderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2237,10 +2237,9 @@ public void RecognizesNamedEventBeingAdded()
Assert.Equal(0, entry.ReferenceFrameIndex);
Assert.Equal("new element", referenceFrames[entry.ReferenceFrameIndex].ElementName);
});
Assert.Collection(batch.AddedNamedEvents.Value.AsEnumerable(),
entry => AssertNamedEvent(entry, 123, 2, "someevent1", "added to existing element"),
entry => AssertNamedEvent(entry, 123, 4, "someevent2", "added with new element"));
Assert.False(batch.RemovedNamedEvents.HasValue);
Assert.Collection(batch.NamedEventChanges.Value.AsEnumerable(),
entry => AssertNamedEventChange(entry, NamedEventChangeType.Added, 123, 2, "someevent1", "added to existing element"),
entry => AssertNamedEventChange(entry, NamedEventChangeType.Added, 123, 4, "someevent2", "added with new element"));
}

[Fact]
Expand All @@ -2264,10 +2263,9 @@ public void RecognizesNamedEventBeingRemoved()
// Assert
Assert.Collection(result.Edits,
entry => AssertEdit(entry, RenderTreeEditType.RemoveFrame, 1));
Assert.False(batch.AddedNamedEvents.HasValue);
Assert.Collection(batch.RemovedNamedEvents.Value.AsEnumerable(),
entry => AssertNamedEvent(entry, 123, 2, "someevent1", "removing from retained element"),
entry => AssertNamedEvent(entry, 123, 4, "someevent2", "removed because element was removed"));
Assert.Collection(batch.NamedEventChanges.Value.AsEnumerable(),
entry => AssertNamedEventChange(entry, NamedEventChangeType.Removed, 123, 2, "someevent1", "removing from retained element"),
entry => AssertNamedEventChange(entry, NamedEventChangeType.Removed, 123, 4, "someevent2", "removed because element was removed"));
}

[Fact]
Expand All @@ -2293,10 +2291,9 @@ public void RecognizesNamedEventBeingMoved()
Assert.Equal(0, entry.ReferenceFrameIndex);
Assert.Equal("attr1", referenceFrames[entry.ReferenceFrameIndex].AttributeName);
});
Assert.Collection(batch.RemovedNamedEvents.Value.AsEnumerable(),
entry => AssertNamedEvent(entry, 123, 1, "eventname", "assigned name"));
Assert.Collection(batch.AddedNamedEvents.Value.AsEnumerable(),
entry => AssertNamedEvent(entry, 123, 2, "eventname", "assigned name"));
Assert.Collection(batch.NamedEventChanges.Value.AsEnumerable(),
entry => AssertNamedEventChange(entry, NamedEventChangeType.Removed, 123, 1, "eventname", "assigned name"),
entry => AssertNamedEventChange(entry, NamedEventChangeType.Added, 123, 2, "eventname", "assigned name"));
}

[Fact]
Expand All @@ -2317,10 +2314,9 @@ public void RecognizesNamedEventChangingAssignedName()

// Assert
Assert.Empty(result.Edits);
Assert.Collection(batch.RemovedNamedEvents.Value.AsEnumerable(),
entry => AssertNamedEvent(entry, 123, 1, "eventname1", "original name"));
Assert.Collection(batch.AddedNamedEvents.Value.AsEnumerable(),
entry => AssertNamedEvent(entry, 123, 1, "eventname1", "changed name"));
Assert.Collection(batch.NamedEventChanges.Value.AsEnumerable(),
entry => AssertNamedEventChange(entry, NamedEventChangeType.Removed, 123, 1, "eventname1", "original name"),
entry => AssertNamedEventChange(entry, NamedEventChangeType.Added, 123, 1, "eventname1", "changed name"));
}

private (RenderTreeDiff, RenderTreeFrame[]) GetSingleUpdatedComponent(bool initializeFromFrames = false)
Expand Down Expand Up @@ -2486,13 +2482,15 @@ private static void AssertPermutationListEntry(
Assert.Equal(toSiblingIndex, edit.MoveToSiblingIndex);
}

private static void AssertNamedEvent(
NamedEvent namedEvent,
private static void AssertNamedEventChange(
NamedEventChange namedEvent,
NamedEventChangeType type,
int componentId,
int frameIndex,
string eventType,
string assignedName)
{
Assert.Equal(type, namedEvent.ChangeType);
Assert.Equal(componentId, namedEvent.ComponentId);
Assert.Equal(frameIndex, namedEvent.FrameIndex);
Assert.Equal(eventType, namedEvent.EventType);
Expand Down
21 changes: 18 additions & 3 deletions src/Components/Components/test/Rendering/RenderTreeBuilderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2270,6 +2270,21 @@ public void CannotAddNamedEventWithNullAssignedName()
Assert.Equal("assignedName", ex.ParamName);
}

[Fact]
public void CannotAddNamedEventWithEmptyAssignedName()
{
// Arrange
var builder = new RenderTreeBuilder();
builder.OpenElement(0, "elem");

// Act/Assert
var ex = Assert.Throws<ArgumentException>(() =>
{
builder.AddNamedEvent(1, "eventtype", "");
});
Assert.Equal("assignedName", ex.ParamName);
}

[Fact]
public void CannotAddAttributesAfterNamedEvent()
{
Expand All @@ -2287,9 +2302,9 @@ public void CannotAddAttributesAfterNamedEvent()
}

[Fact]
public void TemporaryApiForNamedSubmitEventsWorksEvenIfAttributesAddedAfter()
public void TemporaryApiForFormNameEventsWorksEvenIfAttributesAddedAfter()
{
// TODO: Remove this once the Razor compiler is updated to support @onsubmit:name directly
// TODO: Remove this once the Razor compiler is updated to support @formname directly

// Arrange
var builder = new RenderTreeBuilder();
Expand All @@ -2298,7 +2313,7 @@ public void TemporaryApiForNamedSubmitEventsWorksEvenIfAttributesAddedAfter()
// Act
builder.OpenElement(0, "div");
builder.AddAttribute(1, "attr1", 123);
builder.AddAttribute(2, "@onsubmit:name", "some custom name");
builder.AddAttribute(2, "@formname", "some custom name");
builder.AddAttribute(3, "attr2", 456);
builder.OpenElement(4, "other");
builder.CloseElement();
Expand Down
Loading

0 comments on commit e5bb36d

Please sign in to comment.