Support 'ref' syntax for capturing references to elements and components (#685)
This commit is contained in:
parent
a700fa945e
commit
4033560734
|
|
@ -1,11 +1,14 @@
|
||||||
import { platform } from '../Environment';
|
import { platform } from '../Environment';
|
||||||
import { System_String } from '../Platform/Platform';
|
import { System_String } from '../Platform/Platform';
|
||||||
import { getRegisteredFunction } from './RegisteredFunction';
|
import { getRegisteredFunction } from './RegisteredFunction';
|
||||||
|
import { getElementByCaptureId } from '../Rendering/ElementReferenceCapture';
|
||||||
|
|
||||||
|
const elementRefKey = '_blazorElementRef'; // Keep in sync with ElementRef.cs
|
||||||
|
|
||||||
export function invokeWithJsonMarshalling(identifier: System_String, ...argsJson: System_String[]) {
|
export function invokeWithJsonMarshalling(identifier: System_String, ...argsJson: System_String[]) {
|
||||||
const identifierJsString = platform.toJavaScriptString(identifier);
|
const identifierJsString = platform.toJavaScriptString(identifier);
|
||||||
const funcInstance = getRegisteredFunction(identifierJsString);
|
const funcInstance = getRegisteredFunction(identifierJsString);
|
||||||
const args = argsJson.map(json => JSON.parse(platform.toJavaScriptString(json)));
|
const args = argsJson.map(json => JSON.parse(platform.toJavaScriptString(json), jsonReviver));
|
||||||
const result = funcInstance.apply(null, args);
|
const result = funcInstance.apply(null, args);
|
||||||
if (result !== null && result !== undefined) {
|
if (result !== null && result !== undefined) {
|
||||||
const resultJson = JSON.stringify(result);
|
const resultJson = JSON.stringify(result);
|
||||||
|
|
@ -14,3 +17,11 @@ export function invokeWithJsonMarshalling(identifier: System_String, ...argsJson
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function jsonReviver(key: string, value: any): any {
|
||||||
|
if (value && typeof value === 'object' && value.hasOwnProperty(elementRefKey) && typeof value[elementRefKey] === 'number') {
|
||||||
|
return getElementByCaptureId(value[elementRefKey]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import { platform } from '../Environment';
|
||||||
import { EventDelegator } from './EventDelegator';
|
import { EventDelegator } from './EventDelegator';
|
||||||
import { EventForDotNet, UIEventArgs } from './EventForDotNet';
|
import { EventForDotNet, UIEventArgs } from './EventForDotNet';
|
||||||
import { LogicalElement, toLogicalElement, insertLogicalChild, removeLogicalChild, getLogicalParent, getLogicalChild, createAndInsertLogicalContainer, isSvgElement } from './LogicalElements';
|
import { LogicalElement, toLogicalElement, insertLogicalChild, removeLogicalChild, getLogicalParent, getLogicalChild, createAndInsertLogicalContainer, isSvgElement } from './LogicalElements';
|
||||||
|
import { applyCaptureIdToElement } from './ElementReferenceCapture';
|
||||||
const selectValuePropname = '_blazorSelectValue';
|
const selectValuePropname = '_blazorSelectValue';
|
||||||
let raiseEventMethod: MethodHandle;
|
let raiseEventMethod: MethodHandle;
|
||||||
let renderComponentMethod: MethodHandle;
|
let renderComponentMethod: MethodHandle;
|
||||||
|
|
@ -138,6 +139,13 @@ export class BrowserRenderer {
|
||||||
return 1;
|
return 1;
|
||||||
case FrameType.region:
|
case FrameType.region:
|
||||||
return this.insertFrameRange(componentId, parent, childIndex, frames, frameIndex + 1, frameIndex + renderTreeFrame.subtreeLength(frame));
|
return this.insertFrameRange(componentId, parent, childIndex, frames, frameIndex + 1, frameIndex + renderTreeFrame.subtreeLength(frame));
|
||||||
|
case FrameType.elementReferenceCapture:
|
||||||
|
if (parent instanceof Element) {
|
||||||
|
applyCaptureIdToElement(parent, renderTreeFrame.elementReferenceCaptureId(frame));
|
||||||
|
return 0; // A "capture" is a child in the diff, but has no node in the DOM
|
||||||
|
} else {
|
||||||
|
throw new Error('Reference capture frames can only be children of element frames.');
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
const unknownType: never = frameType; // Compile-time verification that the switch was exhaustive
|
const unknownType: never = frameType; // Compile-time verification that the switch was exhaustive
|
||||||
throw new Error(`Unknown frame type: ${unknownType}`);
|
throw new Error(`Unknown frame type: ${unknownType}`);
|
||||||
|
|
@ -252,16 +260,27 @@ export class BrowserRenderer {
|
||||||
childIndex += numChildrenInserted;
|
childIndex += numChildrenInserted;
|
||||||
|
|
||||||
// Skip over any descendants, since they are already dealt with recursively
|
// Skip over any descendants, since they are already dealt with recursively
|
||||||
const subtreeLength = renderTreeFrame.subtreeLength(frame);
|
index += countDescendantFrames(frame);
|
||||||
if (subtreeLength > 1) {
|
|
||||||
index += subtreeLength - 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (childIndex - origChildIndex); // Total number of children inserted
|
return (childIndex - origChildIndex); // Total number of children inserted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function countDescendantFrames(frame: RenderTreeFramePointer): number {
|
||||||
|
switch (renderTreeFrame.frameType(frame)) {
|
||||||
|
// The following frame types have a subtree length. Other frames may use that memory slot
|
||||||
|
// to mean something else, so we must not read it. We should consider having nominal subtypes
|
||||||
|
// of RenderTreeFramePointer that prevent access to non-applicable fields.
|
||||||
|
case FrameType.component:
|
||||||
|
case FrameType.element:
|
||||||
|
case FrameType.region:
|
||||||
|
return renderTreeFrame.subtreeLength(frame) - 1;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function isCheckbox(element: Element) {
|
function isCheckbox(element: Element) {
|
||||||
return element.tagName === 'INPUT' && element.getAttribute('type') === 'checkbox';
|
return element.tagName === 'INPUT' && element.getAttribute('type') === 'checkbox';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
export function applyCaptureIdToElement(element: Element, referenceCaptureId: number) {
|
||||||
|
element.setAttribute(getCaptureIdAttributeName(referenceCaptureId), '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getElementByCaptureId(referenceCaptureId: number) {
|
||||||
|
const selector = `[${getCaptureIdAttributeName(referenceCaptureId)}]`;
|
||||||
|
return document.querySelector(selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCaptureIdAttributeName(referenceCaptureId: number) {
|
||||||
|
return `_bl_${referenceCaptureId}`;
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ export const renderTreeFrame = {
|
||||||
// The properties and memory layout must be kept in sync with the .NET equivalent in RenderTreeFrame.cs
|
// The properties and memory layout must be kept in sync with the .NET equivalent in RenderTreeFrame.cs
|
||||||
frameType: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 4) as FrameType,
|
frameType: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 4) as FrameType,
|
||||||
subtreeLength: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 8) as FrameType,
|
subtreeLength: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 8) as FrameType,
|
||||||
|
elementReferenceCaptureId: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 8),
|
||||||
componentId: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 12),
|
componentId: (frame: RenderTreeFramePointer) => platform.readInt32Field(frame, 12),
|
||||||
elementName: (frame: RenderTreeFramePointer) => platform.readStringField(frame, 16),
|
elementName: (frame: RenderTreeFramePointer) => platform.readStringField(frame, 16),
|
||||||
textContent: (frame: RenderTreeFramePointer) => platform.readStringField(frame, 16),
|
textContent: (frame: RenderTreeFramePointer) => platform.readStringField(frame, 16),
|
||||||
|
|
@ -29,6 +30,7 @@ export enum FrameType {
|
||||||
attribute = 3,
|
attribute = 3,
|
||||||
component = 4,
|
component = 4,
|
||||||
region = 5,
|
region = 5,
|
||||||
|
elementReferenceCapture = 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nominal type to ensure only valid pointers are passed to the renderTreeFrame functions.
|
// Nominal type to ensure only valid pointers are passed to the renderTreeFrame functions.
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,10 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
|
||||||
public static readonly string AddAttribute = nameof(AddAttribute);
|
public static readonly string AddAttribute = nameof(AddAttribute);
|
||||||
|
|
||||||
|
public static readonly string AddElementReferenceCapture = nameof(AddElementReferenceCapture);
|
||||||
|
|
||||||
|
public static readonly string AddComponentReferenceCapture = nameof(AddComponentReferenceCapture);
|
||||||
|
|
||||||
public static readonly string Clear = nameof(Clear);
|
public static readonly string Clear = nameof(Clear);
|
||||||
|
|
||||||
public static readonly string GetFrames = nameof(GetFrames);
|
public static readonly string GetFrames = nameof(GetFrames);
|
||||||
|
|
@ -91,5 +95,10 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
{
|
{
|
||||||
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.Components.EventHandlerAttribute";
|
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.Components.EventHandlerAttribute";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ElementRef
|
||||||
|
{
|
||||||
|
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.ElementRef";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -482,6 +482,39 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void WriteReferenceCapture(CodeRenderingContext context, RefExtensionNode refNode)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refNode == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(refNode));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The runtime node writer moves the call elsewhere. At design time we
|
||||||
|
// just want sufficiently similar code that any unknown-identifier or type
|
||||||
|
// errors will be equivalent
|
||||||
|
var captureTypeName = refNode.IsComponentCapture
|
||||||
|
? refNode.ComponentCaptureTypeName
|
||||||
|
: BlazorApi.ElementRef.FullTypeName;
|
||||||
|
WriteCSharpCode(context, new CSharpCodeIntermediateNode
|
||||||
|
{
|
||||||
|
Source = refNode.Source,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
refNode.IdentifierToken,
|
||||||
|
new IntermediateToken
|
||||||
|
{
|
||||||
|
Kind = TokenKind.CSharp,
|
||||||
|
Content = $" = default({captureTypeName});"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void WriteCSharpToken(CodeRenderingContext context, IntermediateToken token)
|
private void WriteCSharpToken(CodeRenderingContext context, IntermediateToken token)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(token.Content))
|
if (string.IsNullOrWhiteSpace(token.Content))
|
||||||
|
|
|
||||||
|
|
@ -76,11 +76,13 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
builder.Features.Add(new BindLoweringPass());
|
builder.Features.Add(new BindLoweringPass());
|
||||||
builder.Features.Add(new EventHandlerLoweringPass());
|
builder.Features.Add(new EventHandlerLoweringPass());
|
||||||
builder.Features.Add(new ComponentLoweringPass());
|
builder.Features.Add(new ComponentLoweringPass());
|
||||||
|
builder.Features.Add(new RefLoweringPass());
|
||||||
builder.Features.Add(new OrphanTagHelperLoweringPass());
|
builder.Features.Add(new OrphanTagHelperLoweringPass());
|
||||||
|
|
||||||
builder.Features.Add(new ComponentTagHelperDescriptorProvider());
|
builder.Features.Add(new ComponentTagHelperDescriptorProvider());
|
||||||
builder.Features.Add(new BindTagHelperDescriptorProvider());
|
builder.Features.Add(new BindTagHelperDescriptorProvider());
|
||||||
builder.Features.Add(new EventHandlerTagHelperDescriptorProvider());
|
builder.Features.Add(new EventHandlerTagHelperDescriptorProvider());
|
||||||
|
builder.Features.Add(new RefTagHelperDescriptorProvider());
|
||||||
|
|
||||||
if (isDeclarationOnlyCompile)
|
if (isDeclarationOnlyCompile)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -43,5 +43,12 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
|
||||||
public readonly static string TagHelperKind = "Blazor.EventHandler";
|
public readonly static string TagHelperKind = "Blazor.EventHandler";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Ref
|
||||||
|
{
|
||||||
|
public readonly static string TagHelperKind = "Blazor.Ref";
|
||||||
|
|
||||||
|
public static readonly string RuntimeName = "Blazor.None";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,5 +27,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
public abstract void WriteComponentBody(CodeRenderingContext context, ComponentBodyExtensionNode node);
|
public abstract void WriteComponentBody(CodeRenderingContext context, ComponentBodyExtensionNode node);
|
||||||
|
|
||||||
public abstract void WriteComponentAttribute(CodeRenderingContext context, ComponentAttributeExtensionNode node);
|
public abstract void WriteComponentAttribute(CodeRenderingContext context, ComponentAttributeExtensionNode node);
|
||||||
|
|
||||||
|
public abstract void WriteReferenceCapture(CodeRenderingContext context, RefExtensionNode node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
private string _unconsumedHtml;
|
private string _unconsumedHtml;
|
||||||
private List<IntermediateToken> _currentAttributeValues;
|
private List<IntermediateToken> _currentAttributeValues;
|
||||||
private IDictionary<string, PendingAttribute> _currentElementAttributes = new Dictionary<string, PendingAttribute>();
|
private IDictionary<string, PendingAttribute> _currentElementAttributes = new Dictionary<string, PendingAttribute>();
|
||||||
|
private List<RefExtensionNode> _currentElementRefCaptures = new List<RefExtensionNode>();
|
||||||
private int _sourceSequence = 0;
|
private int _sourceSequence = 0;
|
||||||
|
|
||||||
private struct PendingAttribute
|
private struct PendingAttribute
|
||||||
|
|
@ -269,6 +270,15 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
_currentElementAttributes.Clear();
|
_currentElementAttributes.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_currentElementRefCaptures.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var refNode in _currentElementRefCaptures)
|
||||||
|
{
|
||||||
|
WriteAddReferenceCaptureCall(context, refNode);
|
||||||
|
}
|
||||||
|
_currentElementRefCaptures.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
_scopeStack.OpenScope( tagName: nextTag.Data, isComponent: false);
|
_scopeStack.OpenScope( tagName: nextTag.Data, isComponent: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -306,6 +316,39 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void WriteAddReferenceCaptureCall(CodeRenderingContext context, RefExtensionNode refNode)
|
||||||
|
{
|
||||||
|
var codeWriter = context.CodeWriter;
|
||||||
|
|
||||||
|
var methodName = refNode.IsComponentCapture
|
||||||
|
? nameof(BlazorApi.RenderTreeBuilder.AddComponentReferenceCapture)
|
||||||
|
: nameof(BlazorApi.RenderTreeBuilder.AddElementReferenceCapture);
|
||||||
|
codeWriter
|
||||||
|
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{methodName}")
|
||||||
|
.Write((_sourceSequence++).ToString())
|
||||||
|
.WriteParameterSeparator();
|
||||||
|
|
||||||
|
const string refCaptureParamName = "__value";
|
||||||
|
using (var lambdaScope = codeWriter.BuildLambda(refCaptureParamName))
|
||||||
|
{
|
||||||
|
var typecastIfNeeded = refNode.IsComponentCapture ? $"({refNode.ComponentCaptureTypeName})" : string.Empty;
|
||||||
|
WriteCSharpCode(context, new CSharpCodeIntermediateNode
|
||||||
|
{
|
||||||
|
Source = refNode.Source,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
refNode.IdentifierToken,
|
||||||
|
new IntermediateToken {
|
||||||
|
Kind = TokenKind.CSharp,
|
||||||
|
Content = $" = {typecastIfNeeded}{refCaptureParamName};"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
codeWriter.WriteEndMethodInvocation();
|
||||||
|
}
|
||||||
|
|
||||||
private void RejectDisallowedHtmlTags(IntermediateNode node, HtmlTagToken tagToken)
|
private void RejectDisallowedHtmlTags(IntermediateNode node, HtmlTagToken tagToken)
|
||||||
{
|
{
|
||||||
// Disallow <script> in components as per #552
|
// Disallow <script> in components as per #552
|
||||||
|
|
@ -484,6 +527,26 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
context.CodeWriter.WriteLine();
|
context.CodeWriter.WriteLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void WriteReferenceCapture(CodeRenderingContext context, RefExtensionNode node)
|
||||||
|
{
|
||||||
|
if (node.IsComponentCapture)
|
||||||
|
{
|
||||||
|
// AddComponentReferenceCapture calls can be inserted directly because they are
|
||||||
|
// already in the desired place in the IR
|
||||||
|
WriteAddReferenceCaptureCall(context, node);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// AddElementReferenceCapture calls must appear in the RenderTree as children of the element
|
||||||
|
// being captured, so store the RefCapture until we're ready to write its children.
|
||||||
|
// We could instead modify RefLoweringPass to insert the RefExtensionNode at the right
|
||||||
|
// place, but this is harder because it would involve understanding which HtmlContent
|
||||||
|
// node represents the end of the tag. It's simpler to do it here because here we are
|
||||||
|
// already doing HTML parsing to understand the structure.
|
||||||
|
_currentElementRefCaptures.Add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private SourceSpan? CalculateSourcePosition(
|
private SourceSpan? CalculateSourcePosition(
|
||||||
SourceSpan? razorTokenPosition,
|
SourceSpan? razorTokenPosition,
|
||||||
TextPosition htmlNodePosition)
|
TextPosition htmlNodePosition)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
{
|
||||||
|
internal class RefExtensionNode : ExtensionIntermediateNode
|
||||||
|
{
|
||||||
|
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
|
||||||
|
|
||||||
|
public IntermediateToken IdentifierToken { get; }
|
||||||
|
|
||||||
|
public bool IsComponentCapture { get; }
|
||||||
|
|
||||||
|
public string ComponentCaptureTypeName { get; }
|
||||||
|
|
||||||
|
public RefExtensionNode(IntermediateToken identifierToken)
|
||||||
|
{
|
||||||
|
IdentifierToken = identifierToken ?? throw new ArgumentNullException(nameof(identifierToken));
|
||||||
|
Source = IdentifierToken.Source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RefExtensionNode(IntermediateToken identifierToken, string componentCaptureTypeName)
|
||||||
|
: this(identifierToken)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(componentCaptureTypeName))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Cannot be null or empty", nameof(componentCaptureTypeName));
|
||||||
|
}
|
||||||
|
|
||||||
|
IsComponentCapture = true;
|
||||||
|
ComponentCaptureTypeName = componentCaptureTypeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Accept(IntermediateNodeVisitor visitor)
|
||||||
|
{
|
||||||
|
if (visitor == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(visitor));
|
||||||
|
}
|
||||||
|
|
||||||
|
AcceptExtensionNode<RefExtensionNode>(this, visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteNode(CodeTarget target, CodeRenderingContext context)
|
||||||
|
{
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
var writer = (BlazorNodeWriter)context.NodeWriter;
|
||||||
|
writer.WriteReferenceCapture(context, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
{
|
||||||
|
internal class RefLoweringPass : IntermediateNodePassBase, IRazorOptimizationPass
|
||||||
|
{
|
||||||
|
// Run after our other passes
|
||||||
|
public override int Order => 1000;
|
||||||
|
|
||||||
|
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||||
|
{
|
||||||
|
var @namespace = documentNode.FindPrimaryNamespace();
|
||||||
|
var @class = documentNode.FindPrimaryClass();
|
||||||
|
if (@namespace == null || @class == null)
|
||||||
|
{
|
||||||
|
// Nothing to do, bail. We can't function without the standard structure.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodes = documentNode.FindDescendantNodes<TagHelperIntermediateNode>();
|
||||||
|
for (var i = 0; i < nodes.Count; i++)
|
||||||
|
{
|
||||||
|
var node = nodes[i];
|
||||||
|
|
||||||
|
for (var j = node.Children.Count - 1; j >= 0; j--)
|
||||||
|
{
|
||||||
|
var attributeNode = node.Children[j] as ComponentAttributeExtensionNode;
|
||||||
|
if (attributeNode != null &&
|
||||||
|
attributeNode.TagHelper != null &&
|
||||||
|
attributeNode.TagHelper.IsRefTagHelper())
|
||||||
|
{
|
||||||
|
RewriteUsage(@class, node, j, attributeNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RewriteUsage(ClassDeclarationIntermediateNode classNode, TagHelperIntermediateNode node, int index, ComponentAttributeExtensionNode attributeNode)
|
||||||
|
{
|
||||||
|
// If we can't get a nonempty attribute name, do nothing because there will
|
||||||
|
// already be a diagnostic for empty values
|
||||||
|
var identifierToken = DetermineIdentifierToken(attributeNode);
|
||||||
|
if (identifierToken != null)
|
||||||
|
{
|
||||||
|
node.Children.Remove(attributeNode);
|
||||||
|
|
||||||
|
// Determine whether this is an element capture or a component capture, and
|
||||||
|
// if applicable the type name that will appear in the resulting capture code
|
||||||
|
var componentTagHelper = node.TagHelpers.FirstOrDefault(x => x.IsComponentTagHelper());
|
||||||
|
if (componentTagHelper != null)
|
||||||
|
{
|
||||||
|
// For components, the RefExtensionNode must go after all ComponentAttributeExtensionNode
|
||||||
|
// and ComponentBodyExtensionNode siblings because they translate to AddAttribute calls.
|
||||||
|
// We can therefore put it immediately before the ComponentCloseExtensionNode.
|
||||||
|
var componentCloseNodePosition = LastIndexOf(node.Children, n => n is ComponentCloseExtensionNode);
|
||||||
|
if (componentCloseNodePosition < 0)
|
||||||
|
{
|
||||||
|
// Should never happen - would imply we're running the lowering passes in the wrong order
|
||||||
|
throw new InvalidOperationException($"Cannot find {nameof(ComponentCloseExtensionNode)} among ref node siblings.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var refExtensionNode = new RefExtensionNode(identifierToken, componentTagHelper.GetTypeName());
|
||||||
|
node.Children.Insert(componentCloseNodePosition, refExtensionNode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// For elements, it doesn't matter how the RefExtensionNode is positioned
|
||||||
|
// among the children, as the node writer takes care of emitting the
|
||||||
|
// code at the right point after the AddAttribute calls
|
||||||
|
node.Children.Add(new RefExtensionNode(identifierToken));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntermediateToken DetermineIdentifierToken(ComponentAttributeExtensionNode attributeNode)
|
||||||
|
{
|
||||||
|
IntermediateToken foundToken = null;
|
||||||
|
|
||||||
|
if (attributeNode.Children.Count == 1)
|
||||||
|
{
|
||||||
|
if (attributeNode.Children[0] is IntermediateToken token)
|
||||||
|
{
|
||||||
|
foundToken = token;
|
||||||
|
}
|
||||||
|
else if (attributeNode.Children[0] is CSharpExpressionIntermediateNode csharpNode)
|
||||||
|
{
|
||||||
|
if (csharpNode.Children.Count == 1)
|
||||||
|
{
|
||||||
|
foundToken = csharpNode.Children[0] as IntermediateToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !string.IsNullOrWhiteSpace(foundToken?.Content) ? foundToken : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int LastIndexOf<T>(IList<T> items, Predicate<T> predicate)
|
||||||
|
{
|
||||||
|
for (var index = items.Count - 1; index >= 0; index--)
|
||||||
|
{
|
||||||
|
if (predicate(items[index]))
|
||||||
|
{
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
{
|
||||||
|
internal class RefTagHelperDescriptorProvider : ITagHelperDescriptorProvider
|
||||||
|
{
|
||||||
|
// Run after the component tag helper provider, because later we may want component-type-specific variants of this
|
||||||
|
public int Order { get; set; } = 1000;
|
||||||
|
|
||||||
|
public RazorEngine Engine { get; set; }
|
||||||
|
|
||||||
|
public void Execute(TagHelperDescriptorProviderContext context)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Results.Add(CreateRefTagHelper());
|
||||||
|
}
|
||||||
|
|
||||||
|
private TagHelperDescriptor CreateRefTagHelper()
|
||||||
|
{
|
||||||
|
var builder = TagHelperDescriptorBuilder.Create(BlazorMetadata.Ref.TagHelperKind, "Ref", BlazorApi.AssemblyName);
|
||||||
|
builder.Documentation = Resources.RefTagHelper_Documentation;
|
||||||
|
|
||||||
|
builder.Metadata.Add(BlazorMetadata.SpecialKindKey, BlazorMetadata.Ref.TagHelperKind);
|
||||||
|
builder.Metadata[TagHelperMetadata.Runtime.Name] = BlazorMetadata.Ref.RuntimeName;
|
||||||
|
|
||||||
|
// WTE has a bug in 15.7p1 where a Tag Helper without a display-name that looks like
|
||||||
|
// a C# property will crash trying to create the toolips.
|
||||||
|
builder.SetTypeName("Microsoft.AspNetCore.Blazor.Components.Ref");
|
||||||
|
|
||||||
|
builder.TagMatchingRule(rule =>
|
||||||
|
{
|
||||||
|
rule.TagName = "*";
|
||||||
|
rule.Attribute(attribute =>
|
||||||
|
{
|
||||||
|
attribute.Name = "ref";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.BindAttribute(attribute =>
|
||||||
|
{
|
||||||
|
attribute.Documentation = Resources.RefTagHelper_Documentation;
|
||||||
|
attribute.Name = "ref";
|
||||||
|
|
||||||
|
// WTE has a bug 15.7p1 where a Tag Helper without a display-name that looks like
|
||||||
|
// a C# property will crash trying to create the toolips.
|
||||||
|
attribute.SetPropertyName("Ref");
|
||||||
|
attribute.TypeName = typeof(object).FullName;
|
||||||
|
});
|
||||||
|
|
||||||
|
return builder.Build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -203,5 +203,14 @@ namespace Microsoft.AspNetCore.Blazor.Razor {
|
||||||
return ResourceManager.GetString("PageDirectiveCannotBeImported", resourceCulture);
|
return ResourceManager.GetString("PageDirectiveCannotBeImported", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Populates the specified field or property with a reference to the element or component..
|
||||||
|
/// </summary>
|
||||||
|
internal static string RefTagHelper_Documentation {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("RefTagHelper_Documentation", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -165,4 +165,7 @@
|
||||||
<data name="PageDirective_RouteToken_Name" xml:space="preserve">
|
<data name="PageDirective_RouteToken_Name" xml:space="preserve">
|
||||||
<value>route template</value>
|
<value>route template</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="RefTagHelper_Documentation" xml:space="preserve">
|
||||||
|
<value>Populates the specified field or property with a reference to the element or component.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -103,6 +103,18 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
string.Equals(BlazorMetadata.EventHandler.TagHelperKind, kind);
|
string.Equals(BlazorMetadata.EventHandler.TagHelperKind, kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsRefTagHelper(this TagHelperDescriptor tagHelper)
|
||||||
|
{
|
||||||
|
if (tagHelper == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(tagHelper));
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
tagHelper.Metadata.TryGetValue(BlazorMetadata.SpecialKindKey, out var kind) &&
|
||||||
|
string.Equals(BlazorMetadata.Ref.TagHelperKind, kind);
|
||||||
|
}
|
||||||
|
|
||||||
public static string GetEventArgsType(this TagHelperDescriptor tagHelper)
|
public static string GetEventArgsType(this TagHelperDescriptor tagHelper)
|
||||||
{
|
{
|
||||||
if (tagHelper == null)
|
if (tagHelper == null)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Blazor.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a reference to a rendered element.
|
||||||
|
/// </summary>
|
||||||
|
public readonly struct ElementRef : ICustomJsonSerializer
|
||||||
|
{
|
||||||
|
// Static to ensure uniqueness even if there are multiple Renderer instances
|
||||||
|
// This would not be necessary if the JS-side code maintained a lookup from capureId to Element instances,
|
||||||
|
// but we're not doing that presently as it causes more work during disposal to remove those entries
|
||||||
|
// WARNING: Once we support server-side rendering, we should check if running on the server and avoid
|
||||||
|
// populating element reference capture IDs at all, because doing so could (a) eventually
|
||||||
|
// overflow the static int, and (b) disclose information to clients about how many other
|
||||||
|
// requests the server is handling, etc. In general, as part of implementing SSR, we need to
|
||||||
|
// audit the code it calls for any use of statics.
|
||||||
|
private static int _nextId = 0;
|
||||||
|
|
||||||
|
internal int Id { get; }
|
||||||
|
|
||||||
|
private ElementRef(int id)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static ElementRef CreateWithUniqueId()
|
||||||
|
=> new ElementRef(Interlocked.Increment(ref _nextId));
|
||||||
|
|
||||||
|
object ICustomJsonSerializer.ToJsonPrimitive()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "_blazorElementRef", Id }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor.Json
|
||||||
|
{
|
||||||
|
// This is internal because we're trying to avoid expanding JsonUtil into a sophisticated
|
||||||
|
// API. Developers who want that would be better served by using a different JSON package
|
||||||
|
// instead. Also the perf implications of the ICustomJsonSerializer approach aren't ideal
|
||||||
|
// (it forces structs to be boxed, and returning a dictionary means lots more allocations
|
||||||
|
// and boxing of any value-typed properties).
|
||||||
|
internal interface ICustomJsonSerializer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Supplies a representation suitable for JSON serialization. For example, the
|
||||||
|
/// return value may be a string->object dictionary containing properties to
|
||||||
|
/// serialize, or simply a string.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
object ToJsonPrimitive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -66,6 +66,7 @@ using System.Globalization;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Blazor.Json;
|
||||||
using SimpleJson.Reflection;
|
using SimpleJson.Reflection;
|
||||||
|
|
||||||
// ReSharper disable LoopCanBeConvertedToQuery
|
// ReSharper disable LoopCanBeConvertedToQuery
|
||||||
|
|
@ -1497,6 +1498,8 @@ namespace SimpleJson
|
||||||
output = input.ToString();
|
output = input.ToString();
|
||||||
else if (input is TimeSpan)
|
else if (input is TimeSpan)
|
||||||
output = ((TimeSpan)input).ToString("c");
|
output = ((TimeSpan)input).ToString("c");
|
||||||
|
else if (input is ICustomJsonSerializer customJsonSerializer)
|
||||||
|
output = customJsonSerializer.ToJsonPrimitive();
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Enum inputEnum = input as Enum;
|
Enum inputEnum = input as Enum;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
{
|
{
|
||||||
private readonly static object BoxedTrue = true;
|
private readonly static object BoxedTrue = true;
|
||||||
private readonly static object BoxedFalse = false;
|
private readonly static object BoxedFalse = false;
|
||||||
|
private readonly static string ComponentReferenceCaptureInvalidParentMessage = $"Component reference captures may only be added as children of frames of type {RenderTreeFrameType.Component}";
|
||||||
|
|
||||||
private readonly Renderer _renderer;
|
private readonly Renderer _renderer;
|
||||||
private readonly ArrayBuilder<RenderTreeFrame> _entries = new ArrayBuilder<RenderTreeFrame>(10);
|
private readonly ArrayBuilder<RenderTreeFrame> _entries = new ArrayBuilder<RenderTreeFrame>(10);
|
||||||
|
|
@ -356,6 +357,43 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
entry = entry.WithComponentSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
|
entry = entry.WithComponentSubtreeLength(_entries.Count - indexOfEntryBeingClosed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends a frame representing an instruction to capture a reference to the parent element.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||||
|
/// <param name="elementReferenceCaptureAction">An action to be invoked whenever the reference value changes.</param>
|
||||||
|
public void AddElementReferenceCapture(int sequence, Action<ElementRef> elementReferenceCaptureAction)
|
||||||
|
{
|
||||||
|
if (GetCurrentParentFrameType() != RenderTreeFrameType.Element)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Element reference captures may only be added as children of frames of type {RenderTreeFrameType.Element}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Append(RenderTreeFrame.ElementReferenceCapture(sequence, elementReferenceCaptureAction));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends a frame representing an instruction to capture a reference to the parent component.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||||
|
/// <param name="componentReferenceCaptureAction">An action to be invoked whenever the reference value changes.</param>
|
||||||
|
public void AddComponentReferenceCapture(int sequence, Action<object> componentReferenceCaptureAction)
|
||||||
|
{
|
||||||
|
var parentFrameIndex = GetCurrentParentFrameIndex();
|
||||||
|
if (!parentFrameIndex.HasValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(ComponentReferenceCaptureInvalidParentMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentFrameIndexValue = parentFrameIndex.Value;
|
||||||
|
if (_entries.Buffer[parentFrameIndexValue].FrameType != RenderTreeFrameType.Component)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(ComponentReferenceCaptureInvalidParentMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
Append(RenderTreeFrame.ComponentReferenceCapture(sequence, componentReferenceCaptureAction, parentFrameIndexValue));
|
||||||
|
}
|
||||||
|
|
||||||
// Internal for tests
|
// Internal for tests
|
||||||
// Not public because there's no current use case for user code defining regions arbitrarily.
|
// Not public because there's no current use case for user code defining regions arbitrarily.
|
||||||
// Currently the sole use case for regions is when appending a RenderFragment.
|
// Currently the sole use case for regions is when appending a RenderFragment.
|
||||||
|
|
@ -382,6 +420,17 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int? GetCurrentParentFrameIndex()
|
||||||
|
=> _openElementIndices.Count == 0 ? (int?)null : _openElementIndices.Peek();
|
||||||
|
|
||||||
|
private RenderTreeFrameType? GetCurrentParentFrameType()
|
||||||
|
{
|
||||||
|
var parentIndex = GetCurrentParentFrameIndex();
|
||||||
|
return parentIndex.HasValue
|
||||||
|
? _entries.Buffer[parentIndex.Value].FrameType
|
||||||
|
: (RenderTreeFrameType?)null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears the builder.
|
/// Clears the builder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -293,11 +293,17 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
|
|
||||||
private static int NextSiblingIndex(RenderTreeFrame frame, int frameIndex)
|
private static int NextSiblingIndex(RenderTreeFrame frame, int frameIndex)
|
||||||
{
|
{
|
||||||
var subtreeLength = frame.ElementSubtreeLength;
|
switch (frame.FrameType)
|
||||||
var distanceToNextSibling = subtreeLength == 0
|
{
|
||||||
? 1 // For frames that don't have a subtree length set, such as text frames
|
case RenderTreeFrameType.Component:
|
||||||
: subtreeLength; // For element or component frames
|
return frameIndex + frame.ComponentSubtreeLength;
|
||||||
return frameIndex + distanceToNextSibling;
|
case RenderTreeFrameType.Element:
|
||||||
|
return frameIndex + frame.ElementSubtreeLength;
|
||||||
|
case RenderTreeFrameType.Region:
|
||||||
|
return frameIndex + frame.RegionSubtreeLength;
|
||||||
|
default:
|
||||||
|
return frameIndex + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AppendDiffEntriesForFramesWithSameSequence(
|
private static void AppendDiffEntriesForFramesWithSameSequence(
|
||||||
|
|
@ -404,6 +410,16 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case RenderTreeFrameType.ElementReferenceCapture:
|
||||||
|
{
|
||||||
|
// We could preserve the ElementReferenceCaptureId from the old frame to the new frame,
|
||||||
|
// and even call newFrame.ElementReferenceCaptureAction(id) each time in case it wants
|
||||||
|
// to do something different with the ID. However there's no known use case for
|
||||||
|
// that, so presently the rule is that for any given element, the reference
|
||||||
|
// capture action is only invoked once.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// We don't handle attributes here, they have their own diff logic.
|
// We don't handle attributes here, they have their own diff logic.
|
||||||
// See AppendDiffEntriesForAttributeFrame
|
// See AppendDiffEntriesForAttributeFrame
|
||||||
default:
|
default:
|
||||||
|
|
@ -484,6 +500,16 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
diffContext.SiblingIndex++;
|
diffContext.SiblingIndex++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case RenderTreeFrameType.ElementReferenceCapture:
|
||||||
|
{
|
||||||
|
InitializeNewElementReferenceCaptureFrame(ref diffContext, ref newFrame);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RenderTreeFrameType.ComponentReferenceCapture:
|
||||||
|
{
|
||||||
|
InitializeNewComponentReferenceCaptureFrame(ref diffContext, ref newFrame);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -573,6 +599,12 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
case RenderTreeFrameType.Attribute:
|
case RenderTreeFrameType.Attribute:
|
||||||
InitializeNewAttributeFrame(ref diffContext, ref frame);
|
InitializeNewAttributeFrame(ref diffContext, ref frame);
|
||||||
break;
|
break;
|
||||||
|
case RenderTreeFrameType.ElementReferenceCapture:
|
||||||
|
InitializeNewElementReferenceCaptureFrame(ref diffContext, ref frame);
|
||||||
|
break;
|
||||||
|
case RenderTreeFrameType.ComponentReferenceCapture:
|
||||||
|
InitializeNewComponentReferenceCaptureFrame(ref diffContext, ref frame);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -609,6 +641,32 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void InitializeNewElementReferenceCaptureFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame)
|
||||||
|
{
|
||||||
|
var newElementRef = ElementRef.CreateWithUniqueId();
|
||||||
|
newFrame = newFrame.WithElementReferenceCaptureId(newElementRef.Id);
|
||||||
|
newFrame.ElementReferenceCaptureAction(newElementRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void InitializeNewComponentReferenceCaptureFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame)
|
||||||
|
{
|
||||||
|
ref var parentFrame = ref diffContext.NewTree[newFrame.ComponentReferenceCaptureParentFrameIndex];
|
||||||
|
if (parentFrame.FrameType != RenderTreeFrameType.Component)
|
||||||
|
{
|
||||||
|
// Should never happen, but will help with diagnosis if it does
|
||||||
|
throw new InvalidOperationException($"{nameof(RenderTreeFrameType.ComponentReferenceCapture)} frame references invalid parent index.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var componentInstance = parentFrame.Component;
|
||||||
|
if (componentInstance == null)
|
||||||
|
{
|
||||||
|
// Should never happen, but will help with diagnosis if it does
|
||||||
|
throw new InvalidOperationException($"Trying to initialize {nameof(RenderTreeFrameType.ComponentReferenceCapture)} frame before parent component was assigned.");
|
||||||
|
}
|
||||||
|
|
||||||
|
newFrame.ComponentReferenceCaptureAction(componentInstance);
|
||||||
|
}
|
||||||
|
|
||||||
private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
|
private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int startIndex, int endIndexExcl)
|
||||||
{
|
{
|
||||||
for (var i = startIndex; i < endIndexExcl; i++)
|
for (var i = startIndex; i < endIndexExcl; i++)
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Element"/>,
|
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Element"/>,
|
||||||
/// gets a name representing the type of the element. Otherwise, the value is <see langword="null"/>.
|
/// gets a name representing the type of the element. Otherwise, the value is undefined.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[FieldOffset(16)] public readonly string ElementName;
|
[FieldOffset(16)] public readonly string ElementName;
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Text"/>,
|
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Text"/>,
|
||||||
/// gets the content of the text frame. Otherwise, the value is <see langword="null"/>.
|
/// gets the content of the text frame. Otherwise, the value is undefined.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[FieldOffset(16)] public readonly string TextContent;
|
[FieldOffset(16)] public readonly string TextContent;
|
||||||
|
|
||||||
|
|
@ -79,13 +79,13 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
|
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
|
||||||
/// gets the attribute name. Otherwise, the value is <see langword="null"/>.
|
/// gets the attribute name. Otherwise, the value is undefined.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[FieldOffset(16)] public readonly string AttributeName;
|
[FieldOffset(16)] public readonly string AttributeName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
|
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Attribute"/>,
|
||||||
/// gets the attribute value. Otherwise, the value is <see langword="null"/>.
|
/// gets the attribute value. Otherwise, the value is undefined.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[FieldOffset(24)] public readonly object AttributeValue;
|
[FieldOffset(24)] public readonly object AttributeValue;
|
||||||
|
|
||||||
|
|
@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>,
|
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.Component"/>,
|
||||||
/// gets the child component instance. Otherwise, the value is <see langword="null"/>.
|
/// gets the child component instance. Otherwise, the value is undefined.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[FieldOffset(24)] public readonly IComponent Component;
|
[FieldOffset(24)] public readonly IComponent Component;
|
||||||
|
|
||||||
|
|
@ -129,6 +129,44 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[FieldOffset(8)] public readonly int RegionSubtreeLength;
|
[FieldOffset(8)] public readonly int RegionSubtreeLength;
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------
|
||||||
|
// RenderTreeFrameType.ElementReferenceCapture
|
||||||
|
// --------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ElementReferenceCapture"/>,
|
||||||
|
/// gets the ID of the reference capture. Otherwise, the value is undefined.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(8)] public readonly int ElementReferenceCaptureId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ElementReferenceCapture"/>,
|
||||||
|
/// gets the action that writes the reference to its target. Otherwise, the value is undefined.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(16)] public readonly Action<ElementRef> ElementReferenceCaptureAction;
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------
|
||||||
|
// RenderTreeFrameType.ComponentReferenceCapture
|
||||||
|
// --------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ComponentReferenceCapture"/>,
|
||||||
|
/// gets the index of the parent frame representing the component being captured. Otherwise, the value is undefined.
|
||||||
|
/// WARNING: This index can only be used in the context of the frame's original render tree. If the frame is
|
||||||
|
/// copied elsewhere, such as to the ReferenceFrames buffer of a RenderTreeDiff, then the index will
|
||||||
|
/// not relate to entries in that other buffer.
|
||||||
|
/// Currently there's no scenario where this matters, but if there was, we could change all of the subtree
|
||||||
|
/// initialization logic in RenderTreeDiffBuilder to walk the frames hierarchically, then it would know
|
||||||
|
/// the parent index at the point where it wants to initialize the ComponentReferenceCapture frame.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(8)] public readonly int ComponentReferenceCaptureParentFrameIndex;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the <see cref="FrameType"/> property equals <see cref="RenderTreeFrameType.ComponentReferenceCapture"/>,
|
||||||
|
/// gets the action that writes the reference to its target. Otherwise, the value is undefined.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(16)] public readonly Action<object> ComponentReferenceCaptureAction;
|
||||||
|
|
||||||
private RenderTreeFrame(int sequence, string elementName, int elementSubtreeLength)
|
private RenderTreeFrame(int sequence, string elementName, int elementSubtreeLength)
|
||||||
: this()
|
: this()
|
||||||
{
|
{
|
||||||
|
|
@ -189,6 +227,24 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
RegionSubtreeLength = regionSubtreeLength;
|
RegionSubtreeLength = regionSubtreeLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RenderTreeFrame(int sequence, Action<ElementRef> elementReferenceCaptureAction, int elementReferenceCaptureId)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
FrameType = RenderTreeFrameType.ElementReferenceCapture;
|
||||||
|
Sequence = sequence;
|
||||||
|
ElementReferenceCaptureAction = elementReferenceCaptureAction;
|
||||||
|
ElementReferenceCaptureId = elementReferenceCaptureId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RenderTreeFrame(int sequence, Action<object> componentReferenceCaptureAction, int parentFrameIndex)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
FrameType = RenderTreeFrameType.ComponentReferenceCapture;
|
||||||
|
Sequence = sequence;
|
||||||
|
ComponentReferenceCaptureAction = componentReferenceCaptureAction;
|
||||||
|
ComponentReferenceCaptureParentFrameIndex = parentFrameIndex;
|
||||||
|
}
|
||||||
|
|
||||||
internal static RenderTreeFrame Element(int sequence, string elementName)
|
internal static RenderTreeFrame Element(int sequence, string elementName)
|
||||||
=> new RenderTreeFrame(sequence, elementName: elementName, elementSubtreeLength: 0);
|
=> new RenderTreeFrame(sequence, elementName: elementName, elementSubtreeLength: 0);
|
||||||
|
|
||||||
|
|
@ -207,6 +263,12 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
internal static RenderTreeFrame Region(int sequence)
|
internal static RenderTreeFrame Region(int sequence)
|
||||||
=> new RenderTreeFrame(sequence, regionSubtreeLength: 0);
|
=> new RenderTreeFrame(sequence, regionSubtreeLength: 0);
|
||||||
|
|
||||||
|
internal static RenderTreeFrame ElementReferenceCapture(int sequence, Action<ElementRef> elementReferenceCaptureAction)
|
||||||
|
=> new RenderTreeFrame(sequence, elementReferenceCaptureAction: elementReferenceCaptureAction, elementReferenceCaptureId: 0);
|
||||||
|
|
||||||
|
internal static RenderTreeFrame ComponentReferenceCapture(int sequence, Action<object> componentReferenceCaptureAction, int parentFrameIndex)
|
||||||
|
=> new RenderTreeFrame(sequence, componentReferenceCaptureAction: componentReferenceCaptureAction, parentFrameIndex: parentFrameIndex);
|
||||||
|
|
||||||
internal RenderTreeFrame WithElementSubtreeLength(int elementSubtreeLength)
|
internal RenderTreeFrame WithElementSubtreeLength(int elementSubtreeLength)
|
||||||
=> new RenderTreeFrame(Sequence, elementName: ElementName, elementSubtreeLength: elementSubtreeLength);
|
=> new RenderTreeFrame(Sequence, elementName: ElementName, elementSubtreeLength: elementSubtreeLength);
|
||||||
|
|
||||||
|
|
@ -225,6 +287,9 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
internal RenderTreeFrame WithRegionSubtreeLength(int regionSubtreeLength)
|
internal RenderTreeFrame WithRegionSubtreeLength(int regionSubtreeLength)
|
||||||
=> new RenderTreeFrame(Sequence, regionSubtreeLength: regionSubtreeLength);
|
=> new RenderTreeFrame(Sequence, regionSubtreeLength: regionSubtreeLength);
|
||||||
|
|
||||||
|
internal RenderTreeFrame WithElementReferenceCaptureId(int elementReferenceCaptureId)
|
||||||
|
=> new RenderTreeFrame(Sequence, ElementReferenceCaptureAction, elementReferenceCaptureId);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
// Just to be nice for debugging and unit tests.
|
// Just to be nice for debugging and unit tests.
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|
@ -245,6 +310,9 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
|
|
||||||
case RenderTreeFrameType.Text:
|
case RenderTreeFrameType.Text:
|
||||||
return $"Text: (seq={Sequence}, len=n/a) {EscapeNewlines(TextContent)}";
|
return $"Text: (seq={Sequence}, len=n/a) {EscapeNewlines(TextContent)}";
|
||||||
|
|
||||||
|
case RenderTreeFrameType.ElementReferenceCapture:
|
||||||
|
return $"ElementReferenceCapture: (seq={Sequence}, len=n/a) {ElementReferenceCaptureAction}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.ToString();
|
return base.ToString();
|
||||||
|
|
|
||||||
|
|
@ -35,5 +35,15 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
/// fragment are not comparable to sequence numbers outside it.
|
/// fragment are not comparable to sequence numbers outside it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Region = 5,
|
Region = 5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an instruction to capture or update a reference to the parent element.
|
||||||
|
/// </summary>
|
||||||
|
ElementReferenceCapture = 6,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an instruction to capture or update a reference to the parent component.
|
||||||
|
/// </summary>
|
||||||
|
ComponentReferenceCapture = 7,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -689,5 +689,58 @@ namespace Test
|
||||||
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||||
CompileToAssembly(generated);
|
CompileToAssembly(generated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Element_WithRef()
|
||||||
|
{
|
||||||
|
// Arrange/Act
|
||||||
|
var generated = CompileToCSharp(@"
|
||||||
|
<elem attributebefore=""before"" ref=""myElem"" attributeafter=""after"">Hello</elem>
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||||
|
|
||||||
|
void DoSomething() { myElem.GetHashCode(); } // Avoid 'assigned but not used' warning
|
||||||
|
}
|
||||||
|
");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
|
||||||
|
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||||
|
CompileToAssembly(generated);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Component_WithRef()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(Parse(@"
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var generated = CompileToCSharp(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent ParamBefore=""before"" ref=""myInstance"" ParamAfter=""after"" />
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
Test.MyComponent myInstance;
|
||||||
|
|
||||||
|
void DoSomething() { myInstance.GetHashCode(); } // Avoid 'assigned but not used' warning
|
||||||
|
}
|
||||||
|
");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
|
||||||
|
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||||
|
CompileToAssembly(generated);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
|
||||||
IExtensionIntermediateNodeVisitor<ComponentBodyExtensionNode>,
|
IExtensionIntermediateNodeVisitor<ComponentBodyExtensionNode>,
|
||||||
IExtensionIntermediateNodeVisitor<ComponentCloseExtensionNode>,
|
IExtensionIntermediateNodeVisitor<ComponentCloseExtensionNode>,
|
||||||
IExtensionIntermediateNodeVisitor<ComponentOpenExtensionNode>,
|
IExtensionIntermediateNodeVisitor<ComponentOpenExtensionNode>,
|
||||||
IExtensionIntermediateNodeVisitor<RouteAttributeExtensionNode>
|
IExtensionIntermediateNodeVisitor<RouteAttributeExtensionNode>,
|
||||||
|
IExtensionIntermediateNodeVisitor<RefExtensionNode>
|
||||||
{
|
{
|
||||||
private readonly TextWriter _writer;
|
private readonly TextWriter _writer;
|
||||||
|
|
||||||
|
|
@ -288,5 +289,10 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
|
||||||
{
|
{
|
||||||
WriteContentNode(node, node.Template);
|
WriteContentNode(node, node.Template);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IExtensionIntermediateNodeVisitor<RefExtensionNode>.VisitExtension(RefExtensionNode node)
|
||||||
|
{
|
||||||
|
WriteContentNode(node, node.IdentifierToken.Content, node.IsComponentCapture ? node.ComponentCaptureTypeName : "Element");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -942,5 +942,87 @@ namespace Test
|
||||||
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||||
CompileToAssembly(generated);
|
CompileToAssembly(generated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Element_WithRef()
|
||||||
|
{
|
||||||
|
// Arrange/Act
|
||||||
|
var generated = CompileToCSharp(@"
|
||||||
|
<elem attributebefore=""before"" ref=""myElem"" attributeafter=""after"">Hello</elem>
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||||
|
}
|
||||||
|
");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
|
||||||
|
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||||
|
CompileToAssembly(generated);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Component_WithRef()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(Parse(@"
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
// Arrange/Act
|
||||||
|
var generated = CompileToCSharp(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent ParamBefore=""before"" ref=""myInstance"" ParamAfter=""after"" />
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
private Test.MyComponent myInstance;
|
||||||
|
}
|
||||||
|
");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
|
||||||
|
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||||
|
CompileToAssembly(generated);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Component_WithRef_WithChildContent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(Parse(@"
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
// Arrange/Act
|
||||||
|
var generated = CompileToCSharp(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent ref=""myInstance"" SomeProp=""val"">
|
||||||
|
Some <el>further</el> content
|
||||||
|
</MyComponent>
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
private Test.MyComponent myInstance;
|
||||||
|
}
|
||||||
|
");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
|
||||||
|
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||||
|
CompileToAssembly(generated);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 219
|
||||||
|
private void __RazorDirectiveTokenHelpers__() {
|
||||||
|
((System.Action)(() => {
|
||||||
|
global::System.Object __typeHelper = "*, TestAssembly";
|
||||||
|
}
|
||||||
|
))();
|
||||||
|
}
|
||||||
|
#pragma warning restore 219
|
||||||
|
#pragma warning disable 0414
|
||||||
|
private static System.Object __o = null;
|
||||||
|
#pragma warning restore 0414
|
||||||
|
#pragma warning disable 1998
|
||||||
|
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
builder.AddAttribute(-1, "ChildContent", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
|
||||||
|
}
|
||||||
|
));
|
||||||
|
#line 2 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||||
|
myInstance = default(Test.MyComponent);
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
#line 4 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||||
|
|
||||||
|
Test.MyComponent myInstance;
|
||||||
|
|
||||||
|
void DoSomething() { myInstance.GetHashCode(); } // Avoid 'assigned but not used' warning
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
Document -
|
||||||
|
NamespaceDeclaration - - Test
|
||||||
|
UsingDirective - (3:1,1 [12] ) - System
|
||||||
|
UsingDirective - (18:2,1 [32] ) - System.Collections.Generic
|
||||||
|
UsingDirective - (53:3,1 [17] ) - System.Linq
|
||||||
|
UsingDirective - (73:4,1 [28] ) - System.Threading.Tasks
|
||||||
|
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||||
|
DesignTimeDirective -
|
||||||
|
DirectiveToken - (14:0,14 [32] ) - "*, Microsoft.AspNetCore.Blazor"
|
||||||
|
DirectiveToken - (14:0,14 [9] ) - "*, Test"
|
||||||
|
DirectiveToken - (14:0,14 [15] x:\dir\subdir\Test\TestComponent.cshtml) - *, TestAssembly
|
||||||
|
CSharpCode -
|
||||||
|
IntermediateToken - - CSharp - #pragma warning disable 0414
|
||||||
|
CSharpCode -
|
||||||
|
IntermediateToken - - CSharp - private static System.Object __o = null;
|
||||||
|
CSharpCode -
|
||||||
|
IntermediateToken - - CSharp - #pragma warning restore 0414
|
||||||
|
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||||
|
CSharpCode -
|
||||||
|
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||||
|
HtmlContent - (29:0,29 [2] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (29:0,29 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n
|
||||||
|
TagHelper - (31:1,0 [72] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent - TagMode.SelfClosing
|
||||||
|
ComponentOpenExtensionNode - - Test.MyComponent
|
||||||
|
ComponentAttributeExtensionNode - - ParamBefore -
|
||||||
|
HtmlContent - (57:1,26 [6] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (57:1,26 [6] x:\dir\subdir\Test\TestComponent.cshtml) - Html - before
|
||||||
|
ComponentAttributeExtensionNode - - ParamAfter -
|
||||||
|
HtmlContent - (94:1,63 [5] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (94:1,63 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - after
|
||||||
|
ComponentBodyExtensionNode -
|
||||||
|
RefExtensionNode - (70:1,39 [10] x:\dir\subdir\Test\TestComponent.cshtml) - myInstance - Test.MyComponent
|
||||||
|
ComponentCloseExtensionNode -
|
||||||
|
HtmlContent - (103:1,72 [4] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (103:1,72 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n\n
|
||||||
|
HtmlContent - (253:7,1 [2] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (253:7,1 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n
|
||||||
|
CSharpCode - (119:3,12 [133] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (119:3,12 [133] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n Test.MyComponent myInstance;\n\n void DoSomething() { myInstance.GetHashCode(); } // Avoid 'assigned but not used' warning\n
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
Source Location: (14:0,14 [15] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|*, TestAssembly|
|
||||||
|
Generated Location: (468:14,38 [15] )
|
||||||
|
|*, TestAssembly|
|
||||||
|
|
||||||
|
Source Location: (70:1,39 [10] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|myInstance|
|
||||||
|
Generated Location: (1132:30,39 [10] )
|
||||||
|
|myInstance|
|
||||||
|
|
||||||
|
Source Location: (119:3,12 [133] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|
|
||||||
|
Test.MyComponent myInstance;
|
||||||
|
|
||||||
|
void DoSomething() { myInstance.GetHashCode(); } // Avoid 'assigned but not used' warning
|
||||||
|
|
|
||||||
|
Generated Location: (1316:37,12 [133] )
|
||||||
|
|
|
||||||
|
Test.MyComponent myInstance;
|
||||||
|
|
||||||
|
void DoSomething() { myInstance.GetHashCode(); } // Avoid 'assigned but not used' warning
|
||||||
|
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 219
|
||||||
|
private void __RazorDirectiveTokenHelpers__() {
|
||||||
|
}
|
||||||
|
#pragma warning restore 219
|
||||||
|
#pragma warning disable 0414
|
||||||
|
private static System.Object __o = null;
|
||||||
|
#pragma warning restore 0414
|
||||||
|
#pragma warning disable 1998
|
||||||
|
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||||
|
myElem = default(Microsoft.AspNetCore.Blazor.ElementRef);
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
#line 3 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||||
|
|
||||||
|
Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||||
|
|
||||||
|
void DoSomething() { myElem.GetHashCode(); } // Avoid 'assigned but not used' warning
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
Document -
|
||||||
|
NamespaceDeclaration - - Test
|
||||||
|
UsingDirective - (3:1,1 [12] ) - System
|
||||||
|
UsingDirective - (18:2,1 [32] ) - System.Collections.Generic
|
||||||
|
UsingDirective - (53:3,1 [17] ) - System.Linq
|
||||||
|
UsingDirective - (73:4,1 [28] ) - System.Threading.Tasks
|
||||||
|
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||||
|
DesignTimeDirective -
|
||||||
|
DirectiveToken - (14:0,14 [32] ) - "*, Microsoft.AspNetCore.Blazor"
|
||||||
|
DirectiveToken - (14:0,14 [9] ) - "*, Test"
|
||||||
|
CSharpCode -
|
||||||
|
IntermediateToken - - CSharp - #pragma warning disable 0414
|
||||||
|
CSharpCode -
|
||||||
|
IntermediateToken - - CSharp - private static System.Object __o = null;
|
||||||
|
CSharpCode -
|
||||||
|
IntermediateToken - - CSharp - #pragma warning restore 0414
|
||||||
|
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||||
|
CSharpCode -
|
||||||
|
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - <elem
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - attributebefore="
|
||||||
|
IntermediateToken - (23:0,23 [6] x:\dir\subdir\Test\TestComponent.cshtml) - Html - before
|
||||||
|
IntermediateToken - - Html - "
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - attributeafter="
|
||||||
|
IntermediateToken - (60:0,60 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - after
|
||||||
|
IntermediateToken - - Html - "
|
||||||
|
RefExtensionNode - (36:0,36 [6] x:\dir\subdir\Test\TestComponent.cshtml) - myElem - Element
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - >
|
||||||
|
HtmlContent - (67:0,67 [5] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (67:0,67 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - Hello
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - </elem>
|
||||||
|
HtmlContent - (79:0,79 [4] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (79:0,79 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n\n
|
||||||
|
HtmlContent - (243:6,1 [2] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (243:6,1 [2] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n
|
||||||
|
CSharpCode - (95:2,12 [147] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (95:2,12 [147] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n Microsoft.AspNetCore.Blazor.ElementRef myElem;\n\n void DoSomething() { myElem.GetHashCode(); } // Avoid 'assigned but not used' warning\n
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
Source Location: (36:0,36 [6] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|myElem|
|
||||||
|
Generated Location: (865:23,36 [6] )
|
||||||
|
|myElem|
|
||||||
|
|
||||||
|
Source Location: (95:2,12 [147] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|
|
||||||
|
Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||||
|
|
||||||
|
void DoSomething() { myElem.GetHashCode(); } // Avoid 'assigned but not used' warning
|
||||||
|
|
|
||||||
|
Generated Location: (1067:30,12 [147] )
|
||||||
|
|
|
||||||
|
Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||||
|
|
||||||
|
void DoSomething() { myElem.GetHashCode(); } // Avoid 'assigned but not used' warning
|
||||||
|
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 1998
|
||||||
|
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
builder.OpenComponent<Test.MyComponent>(0);
|
||||||
|
builder.AddAttribute(1, "ParamBefore", "before");
|
||||||
|
builder.AddAttribute(2, "ParamAfter", "after");
|
||||||
|
builder.AddComponentReferenceCapture(3, (__value) => {
|
||||||
|
#line 2 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||||
|
myInstance = (Test.MyComponent)__value;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
);
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
#line 4 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||||
|
|
||||||
|
private Test.MyComponent myInstance;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
Document -
|
||||||
|
NamespaceDeclaration - - Test
|
||||||
|
UsingDirective - (3:1,1 [14] ) - System
|
||||||
|
UsingDirective - (18:2,1 [34] ) - System.Collections.Generic
|
||||||
|
UsingDirective - (53:3,1 [19] ) - System.Linq
|
||||||
|
UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks
|
||||||
|
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||||
|
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||||
|
CSharpCode -
|
||||||
|
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||||
|
TagHelper - (31:1,0 [72] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent - TagMode.SelfClosing
|
||||||
|
ComponentOpenExtensionNode - - Test.MyComponent
|
||||||
|
ComponentAttributeExtensionNode - - ParamBefore -
|
||||||
|
HtmlContent - (57:1,26 [6] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (57:1,26 [6] x:\dir\subdir\Test\TestComponent.cshtml) - Html - before
|
||||||
|
ComponentAttributeExtensionNode - - ParamAfter -
|
||||||
|
HtmlContent - (94:1,63 [5] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (94:1,63 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - after
|
||||||
|
ComponentBodyExtensionNode -
|
||||||
|
RefExtensionNode - (70:1,39 [10] x:\dir\subdir\Test\TestComponent.cshtml) - myInstance - Test.MyComponent
|
||||||
|
ComponentCloseExtensionNode -
|
||||||
|
CSharpCode - (119:3,12 [44] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (119:3,12 [44] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n private Test.MyComponent myInstance;\n
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
Source Location: (70:1,39 [10] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|myInstance|
|
||||||
|
Generated Location: (849:20,39 [10] )
|
||||||
|
|myInstance|
|
||||||
|
|
||||||
|
Source Location: (119:3,12 [44] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|
|
||||||
|
private Test.MyComponent myInstance;
|
||||||
|
|
|
||||||
|
Generated Location: (1103:30,12 [44] )
|
||||||
|
|
|
||||||
|
private Test.MyComponent myInstance;
|
||||||
|
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 1998
|
||||||
|
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
builder.OpenComponent<Test.MyComponent>(0);
|
||||||
|
builder.AddAttribute(1, "SomeProp", "val");
|
||||||
|
builder.AddAttribute(2, "ChildContent", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
|
||||||
|
builder2.AddContent(3, "\n Some ");
|
||||||
|
builder2.OpenElement(4, "el");
|
||||||
|
builder2.AddContent(5, "further");
|
||||||
|
builder2.CloseElement();
|
||||||
|
builder2.AddContent(6, " content\n");
|
||||||
|
}
|
||||||
|
));
|
||||||
|
builder.AddComponentReferenceCapture(7, (__value) => {
|
||||||
|
#line 2 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||||
|
myInstance = (Test.MyComponent)__value;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
);
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
#line 6 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||||
|
|
||||||
|
private Test.MyComponent myInstance;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
Document -
|
||||||
|
NamespaceDeclaration - - Test
|
||||||
|
UsingDirective - (3:1,1 [14] ) - System
|
||||||
|
UsingDirective - (18:2,1 [34] ) - System.Collections.Generic
|
||||||
|
UsingDirective - (53:3,1 [19] ) - System.Linq
|
||||||
|
UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks
|
||||||
|
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||||
|
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||||
|
CSharpCode -
|
||||||
|
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||||
|
TagHelper - (31:1,0 [96] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent - TagMode.StartTagAndEndTag
|
||||||
|
ComponentOpenExtensionNode - - Test.MyComponent
|
||||||
|
ComponentAttributeExtensionNode - - SomeProp -
|
||||||
|
HtmlContent - (71:1,40 [3] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (71:1,40 [3] x:\dir\subdir\Test\TestComponent.cshtml) - Html - val
|
||||||
|
ComponentBodyExtensionNode -
|
||||||
|
HtmlContent - (76:1,45 [37] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (76:1,45 [11] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n Some
|
||||||
|
IntermediateToken - (87:2,9 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - <el>
|
||||||
|
IntermediateToken - (91:2,13 [7] x:\dir\subdir\Test\TestComponent.cshtml) - Html - further
|
||||||
|
IntermediateToken - (98:2,20 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - </el>
|
||||||
|
IntermediateToken - (103:2,25 [10] x:\dir\subdir\Test\TestComponent.cshtml) - Html - content\n
|
||||||
|
RefExtensionNode - (49:1,18 [10] x:\dir\subdir\Test\TestComponent.cshtml) - myInstance - Test.MyComponent
|
||||||
|
ComponentCloseExtensionNode -
|
||||||
|
CSharpCode - (143:5,12 [44] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (143:5,12 [44] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n private Test.MyComponent myInstance;\n
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
Source Location: (49:1,18 [10] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|myInstance|
|
||||||
|
Generated Location: (1160:28,18 [10] )
|
||||||
|
|myInstance|
|
||||||
|
|
||||||
|
Source Location: (143:5,12 [44] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|
|
||||||
|
private Test.MyComponent myInstance;
|
||||||
|
|
|
||||||
|
Generated Location: (1414:38,12 [44] )
|
||||||
|
|
|
||||||
|
private Test.MyComponent myInstance;
|
||||||
|
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 1998
|
||||||
|
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
builder.OpenElement(0, "elem");
|
||||||
|
builder.AddAttribute(1, "attributebefore", "before");
|
||||||
|
builder.AddAttribute(2, "attributeafter", "after");
|
||||||
|
builder.AddElementReferenceCapture(3, (__value) => {
|
||||||
|
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||||
|
myElem = __value;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
);
|
||||||
|
builder.AddContent(4, "Hello");
|
||||||
|
builder.CloseElement();
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
#line 3 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||||
|
|
||||||
|
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
Document -
|
||||||
|
NamespaceDeclaration - - Test
|
||||||
|
UsingDirective - (3:1,1 [14] ) - System
|
||||||
|
UsingDirective - (18:2,1 [34] ) - System.Collections.Generic
|
||||||
|
UsingDirective - (53:3,1 [19] ) - System.Linq
|
||||||
|
UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks
|
||||||
|
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||||
|
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||||
|
CSharpCode -
|
||||||
|
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - <elem
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - attributebefore="
|
||||||
|
IntermediateToken - (23:0,23 [6] x:\dir\subdir\Test\TestComponent.cshtml) - Html - before
|
||||||
|
IntermediateToken - - Html - "
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - attributeafter="
|
||||||
|
IntermediateToken - (60:0,60 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - after
|
||||||
|
IntermediateToken - - Html - "
|
||||||
|
RefExtensionNode - (36:0,36 [6] x:\dir\subdir\Test\TestComponent.cshtml) - myElem - Element
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - >
|
||||||
|
HtmlContent - (67:0,67 [5] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (67:0,67 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - Hello
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - </elem>
|
||||||
|
CSharpCode - (95:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (95:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n private Microsoft.AspNetCore.Blazor.ElementRef myElem;\n
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
Source Location: (36:0,36 [6] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|myElem|
|
||||||
|
Generated Location: (840:20,36 [6] )
|
||||||
|
|myElem|
|
||||||
|
|
||||||
|
Source Location: (95:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|
|
||||||
|
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||||
|
|
|
||||||
|
Generated Location: (1115:31,12 [62] )
|
||||||
|
|
|
||||||
|
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||||
|
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 1998
|
||||||
|
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
builder.OpenElement(0, "elem");
|
||||||
|
builder.AddElementReferenceCapture(1, (__value) => {
|
||||||
|
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||||
|
@class = __value;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
);
|
||||||
|
builder.CloseElement();
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
#line 3 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||||
|
|
||||||
|
private Microsoft.AspNetCore.Blazor.ElementRef @class;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
Document -
|
||||||
|
NamespaceDeclaration - - Test
|
||||||
|
UsingDirective - (3:1,1 [14] ) - System
|
||||||
|
UsingDirective - (18:2,1 [34] ) - System.Collections.Generic
|
||||||
|
UsingDirective - (53:3,1 [19] ) - System.Linq
|
||||||
|
UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks
|
||||||
|
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||||
|
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||||
|
CSharpCode -
|
||||||
|
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - <elem
|
||||||
|
RefExtensionNode - (13:0,13 [6] x:\dir\subdir\Test\TestComponent.cshtml) - @class - Element
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - />
|
||||||
|
CSharpCode - (40:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (40:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n private Microsoft.AspNetCore.Blazor.ElementRef @class;\n
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
Source Location: (13:0,13 [6] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|@class|
|
||||||
|
Generated Location: (685:18,13 [6] )
|
||||||
|
|@class|
|
||||||
|
|
||||||
|
Source Location: (40:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|
|
||||||
|
private Microsoft.AspNetCore.Blazor.ElementRef @class;
|
||||||
|
|
|
||||||
|
Generated Location: (915:28,12 [62] )
|
||||||
|
|
|
||||||
|
private Microsoft.AspNetCore.Blazor.ElementRef @class;
|
||||||
|
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 1998
|
||||||
|
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
builder.OpenElement(0, "elem");
|
||||||
|
builder.AddAttribute(1, "attributebefore", "before");
|
||||||
|
builder.AddAttribute(2, "attributeafter", "after");
|
||||||
|
builder.AddElementReferenceCapture(3, (__value) => {
|
||||||
|
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||||
|
myElem = __value;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
);
|
||||||
|
builder.AddContent(4, "Hello");
|
||||||
|
builder.CloseElement();
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
#line 3 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||||
|
|
||||||
|
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
Document -
|
||||||
|
NamespaceDeclaration - - Test
|
||||||
|
UsingDirective - (3:1,1 [14] ) - System
|
||||||
|
UsingDirective - (18:2,1 [34] ) - System.Collections.Generic
|
||||||
|
UsingDirective - (53:3,1 [19] ) - System.Linq
|
||||||
|
UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks
|
||||||
|
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||||
|
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||||
|
CSharpCode -
|
||||||
|
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - <elem
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - attributebefore="
|
||||||
|
IntermediateToken - (23:0,23 [6] x:\dir\subdir\Test\TestComponent.cshtml) - Html - before
|
||||||
|
IntermediateToken - - Html - "
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - attributeafter="
|
||||||
|
IntermediateToken - (61:0,61 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - after
|
||||||
|
IntermediateToken - - Html - "
|
||||||
|
RefExtensionNode - (37:0,37 [6] x:\dir\subdir\Test\TestComponent.cshtml) - myElem - Element
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - >
|
||||||
|
HtmlContent - (68:0,68 [5] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (68:0,68 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - Hello
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - </elem>
|
||||||
|
CSharpCode - (96:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (96:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n private Microsoft.AspNetCore.Blazor.ElementRef myElem;\n
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
Source Location: (37:0,37 [6] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|myElem|
|
||||||
|
Generated Location: (841:20,37 [6] )
|
||||||
|
|myElem|
|
||||||
|
|
||||||
|
Source Location: (96:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|
|
||||||
|
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||||
|
|
|
||||||
|
Generated Location: (1116:31,12 [62] )
|
||||||
|
|
|
||||||
|
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||||
|
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 1998
|
||||||
|
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
builder.OpenElement(0, "elem");
|
||||||
|
builder.AddAttribute(1, "attributebefore", "before");
|
||||||
|
builder.AddAttribute(2, "attributeafter", "after");
|
||||||
|
builder.AddElementReferenceCapture(3, (__value) => {
|
||||||
|
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||||
|
myElem = __value;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
);
|
||||||
|
builder.AddContent(4, "Hello");
|
||||||
|
builder.CloseElement();
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
#line 3 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||||
|
|
||||||
|
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
Document -
|
||||||
|
NamespaceDeclaration - - Test
|
||||||
|
UsingDirective - (3:1,1 [14] ) - System
|
||||||
|
UsingDirective - (18:2,1 [34] ) - System.Collections.Generic
|
||||||
|
UsingDirective - (53:3,1 [19] ) - System.Linq
|
||||||
|
UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks
|
||||||
|
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Blazor.Components.BlazorComponent -
|
||||||
|
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||||
|
CSharpCode -
|
||||||
|
IntermediateToken - - CSharp - base.BuildRenderTree(builder);
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - <elem
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - attributebefore="
|
||||||
|
IntermediateToken - (23:0,23 [6] x:\dir\subdir\Test\TestComponent.cshtml) - Html - before
|
||||||
|
IntermediateToken - - Html - "
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - attributeafter="
|
||||||
|
IntermediateToken - (60:0,60 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - after
|
||||||
|
IntermediateToken - - Html - "
|
||||||
|
RefExtensionNode - (36:0,36 [6] x:\dir\subdir\Test\TestComponent.cshtml) - myElem - Element
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - >
|
||||||
|
HtmlContent - (67:0,67 [5] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (67:0,67 [5] x:\dir\subdir\Test\TestComponent.cshtml) - Html - Hello
|
||||||
|
HtmlContent -
|
||||||
|
IntermediateToken - - Html - </elem>
|
||||||
|
CSharpCode - (95:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
IntermediateToken - (95:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n private Microsoft.AspNetCore.Blazor.ElementRef myElem;\n
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
Source Location: (36:0,36 [6] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|myElem|
|
||||||
|
Generated Location: (840:20,36 [6] )
|
||||||
|
|myElem|
|
||||||
|
|
||||||
|
Source Location: (95:2,12 [62] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||||
|
|
|
||||||
|
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||||
|
|
|
||||||
|
Generated Location: (1115:31,12 [62] )
|
||||||
|
|
|
||||||
|
private Microsoft.AspNetCore.Blazor.ElementRef myElem;
|
||||||
|
|
|
||||||
|
|
||||||
|
|
@ -299,5 +299,76 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
||||||
var appElement = MountTestComponent<LogicalElementInsertionCases>();
|
var appElement = MountTestComponent<LogicalElementInsertionCases>();
|
||||||
Assert.Equal("First Second Third", appElement.Text);
|
Assert.Equal("First Second Third", appElement.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanUseJsInteropToReferenceElements()
|
||||||
|
{
|
||||||
|
var appElement = MountTestComponent<ElementRefComponent>();
|
||||||
|
var inputElement = appElement.FindElement(By.Id("capturedElement"));
|
||||||
|
var buttonElement = appElement.FindElement(By.TagName("button"));
|
||||||
|
|
||||||
|
Assert.Equal(string.Empty, inputElement.GetAttribute("value"));
|
||||||
|
|
||||||
|
buttonElement.Click();
|
||||||
|
Assert.Equal("Clicks: 1", inputElement.GetAttribute("value"));
|
||||||
|
buttonElement.Click();
|
||||||
|
Assert.Equal("Clicks: 2", inputElement.GetAttribute("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanCaptureReferencesToDynamicallyAddedElements()
|
||||||
|
{
|
||||||
|
var appElement = MountTestComponent<ElementRefComponent>();
|
||||||
|
var buttonElement = appElement.FindElement(By.TagName("button"));
|
||||||
|
var checkbox = appElement.FindElement(By.CssSelector("input[type=checkbox]"));
|
||||||
|
|
||||||
|
// We're going to remove the input. But first, put in some contents
|
||||||
|
// so we can observe it's not the same instance later
|
||||||
|
appElement.FindElement(By.Id("capturedElement")).SendKeys("some text");
|
||||||
|
|
||||||
|
// Remove the captured element
|
||||||
|
checkbox.Click();
|
||||||
|
Assert.Empty(appElement.FindElements(By.Id("capturedElement")));
|
||||||
|
|
||||||
|
// Re-add it; observe it starts empty again
|
||||||
|
checkbox.Click();
|
||||||
|
var inputElement = appElement.FindElement(By.Id("capturedElement"));
|
||||||
|
Assert.NotNull(inputElement);
|
||||||
|
Assert.Equal(string.Empty, inputElement.GetAttribute("value"));
|
||||||
|
|
||||||
|
// See that the capture variable was automatically updated to reference the new instance
|
||||||
|
buttonElement.Click();
|
||||||
|
Assert.Equal("Clicks: 1", inputElement.GetAttribute("value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanCaptureReferencesToDynamicallyAddedComponents()
|
||||||
|
{
|
||||||
|
var appElement = MountTestComponent<ComponentRefComponent>();
|
||||||
|
var incrementButtonSelector = By.CssSelector("#child-component button");
|
||||||
|
var currentCountTextSelector = By.CssSelector("#child-component p:first-of-type");
|
||||||
|
var resetButton = appElement.FindElement(By.Id("reset-child"));
|
||||||
|
var toggleChildCheckbox = appElement.FindElement(By.Id("toggle-child"));
|
||||||
|
|
||||||
|
// Verify the reference was captured initially
|
||||||
|
appElement.FindElement(incrementButtonSelector).Click();
|
||||||
|
Assert.Equal("Current count: 1", appElement.FindElement(currentCountTextSelector).Text);
|
||||||
|
resetButton.Click();
|
||||||
|
Assert.Equal("Current count: 0", appElement.FindElement(currentCountTextSelector).Text);
|
||||||
|
appElement.FindElement(incrementButtonSelector).Click();
|
||||||
|
Assert.Equal("Current count: 1", appElement.FindElement(currentCountTextSelector).Text);
|
||||||
|
|
||||||
|
// Remove and re-add a new instance of the child, checking the text was reset
|
||||||
|
toggleChildCheckbox.Click();
|
||||||
|
Assert.Empty(appElement.FindElements(incrementButtonSelector));
|
||||||
|
toggleChildCheckbox.Click();
|
||||||
|
Assert.Equal("Current count: 0", appElement.FindElement(currentCountTextSelector).Text);
|
||||||
|
|
||||||
|
// Verify we have a new working reference
|
||||||
|
appElement.FindElement(incrementButtonSelector).Click();
|
||||||
|
Assert.Equal("Current count: 1", appElement.FindElement(currentCountTextSelector).Text);
|
||||||
|
resetButton.Click();
|
||||||
|
Assert.Equal("Current count: 0", appElement.FindElement(currentCountTextSelector).Text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
|
using System.Linq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor.Razor.Extensions.Test
|
||||||
|
{
|
||||||
|
public class RefTagHelperDescriptorProviderTest : BaseTagHelperDescriptorProviderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Execute_CreatesDescriptor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = TagHelperDescriptorProviderContext.Create();
|
||||||
|
var provider = new RefTagHelperDescriptorProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.Execute(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var matches = context.Results.Where(result => result.IsRefTagHelper());
|
||||||
|
var item = Assert.Single(matches);
|
||||||
|
|
||||||
|
Assert.Empty(item.AllowedChildTags);
|
||||||
|
Assert.Null(item.TagOutputHint);
|
||||||
|
Assert.Empty(item.Diagnostics);
|
||||||
|
Assert.False(item.HasErrors);
|
||||||
|
Assert.Equal(BlazorMetadata.Ref.TagHelperKind, item.Kind);
|
||||||
|
Assert.Equal(BlazorMetadata.Ref.RuntimeName, item.Metadata[TagHelperMetadata.Runtime.Name]);
|
||||||
|
Assert.False(item.IsDefaultKind());
|
||||||
|
Assert.False(item.KindUsesDefaultTagHelperRuntime());
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
"Populates the specified field or property with a reference to the element or component.",
|
||||||
|
item.Documentation);
|
||||||
|
|
||||||
|
Assert.Equal("Microsoft.AspNetCore.Blazor", item.AssemblyName);
|
||||||
|
Assert.Equal("Ref", item.Name);
|
||||||
|
Assert.Equal("Microsoft.AspNetCore.Blazor.Components.Ref", item.DisplayName);
|
||||||
|
Assert.Equal("Microsoft.AspNetCore.Blazor.Components.Ref", item.GetTypeName());
|
||||||
|
|
||||||
|
// The tag matching rule for a ref is just the attribute name "ref"
|
||||||
|
var rule = Assert.Single(item.TagMatchingRules);
|
||||||
|
Assert.Empty(rule.Diagnostics);
|
||||||
|
Assert.False(rule.HasErrors);
|
||||||
|
Assert.Null(rule.ParentTag);
|
||||||
|
Assert.Equal("*", rule.TagName);
|
||||||
|
Assert.Equal(TagStructure.Unspecified, rule.TagStructure);
|
||||||
|
|
||||||
|
var requiredAttribute = Assert.Single(rule.Attributes);
|
||||||
|
Assert.Empty(requiredAttribute.Diagnostics);
|
||||||
|
Assert.Equal("ref", requiredAttribute.DisplayName);
|
||||||
|
Assert.Equal("ref", requiredAttribute.Name);
|
||||||
|
Assert.Equal(RequiredAttributeDescriptor.NameComparisonMode.FullMatch, requiredAttribute.NameComparison);
|
||||||
|
Assert.Null(requiredAttribute.Value);
|
||||||
|
Assert.Equal(RequiredAttributeDescriptor.ValueComparisonMode.None, requiredAttribute.ValueComparison);
|
||||||
|
|
||||||
|
var attribute = Assert.Single(item.BoundAttributes);
|
||||||
|
Assert.Empty(attribute.Diagnostics);
|
||||||
|
Assert.False(attribute.HasErrors);
|
||||||
|
Assert.Equal(BlazorMetadata.Ref.TagHelperKind, attribute.Kind);
|
||||||
|
Assert.False(attribute.IsDefaultKind());
|
||||||
|
Assert.False(attribute.HasIndexer);
|
||||||
|
Assert.Null(attribute.IndexerNamePrefix);
|
||||||
|
Assert.Null(attribute.IndexerTypeName);
|
||||||
|
Assert.False(attribute.IsIndexerBooleanProperty);
|
||||||
|
Assert.False(attribute.IsIndexerStringProperty);
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
"Populates the specified field or property with a reference to the element or component.",
|
||||||
|
attribute.Documentation);
|
||||||
|
|
||||||
|
Assert.Equal("ref", attribute.Name);
|
||||||
|
Assert.Equal("Ref", attribute.GetPropertyName());
|
||||||
|
Assert.Equal("object Microsoft.AspNetCore.Blazor.Components.Ref.Ref", attribute.DisplayName);
|
||||||
|
Assert.Equal("System.Object", attribute.TypeName);
|
||||||
|
Assert.False(attribute.IsStringProperty);
|
||||||
|
Assert.False(attribute.IsBooleanProperty);
|
||||||
|
Assert.False(attribute.IsEnum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Blazor.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -132,6 +133,16 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
exception.Message);
|
exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SupportsInternalCustomSerializer()
|
||||||
|
{
|
||||||
|
// Arrange/Act
|
||||||
|
var json = JsonUtil.Serialize(new WithCustomSerializer());
|
||||||
|
|
||||||
|
// Asssert
|
||||||
|
Assert.Equal("{\"key1\":\"value1\",\"key2\":123}", json);
|
||||||
|
}
|
||||||
|
|
||||||
class NonEmptyConstructorPoco
|
class NonEmptyConstructorPoco
|
||||||
{
|
{
|
||||||
public NonEmptyConstructorPoco(int parameter) {}
|
public NonEmptyConstructorPoco(int parameter) {}
|
||||||
|
|
@ -158,5 +169,17 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Hobbies { Reading = 1, Swordfighting = 2 }
|
enum Hobbies { Reading = 1, Swordfighting = 2 }
|
||||||
|
|
||||||
|
class WithCustomSerializer : ICustomJsonSerializer
|
||||||
|
{
|
||||||
|
public object ToJsonPrimitive()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "key1", "value1" },
|
||||||
|
{ "key2", 123 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Blazor.Test.Helpers;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Extensions;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Test
|
namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
{
|
{
|
||||||
|
|
@ -260,6 +259,36 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CannotAddAttributeToElementReferenceCapture()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
builder.OpenElement(0, "some element");
|
||||||
|
builder.AddElementReferenceCapture(1, _ => { });
|
||||||
|
builder.AddAttribute(2, "name", "value");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CannotAddAttributeToComponentReferenceCapture()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
builder.OpenComponent<TestComponent>(0);
|
||||||
|
builder.AddComponentReferenceCapture(1, _ => { });
|
||||||
|
builder.AddAttribute(2, "name", "value");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanAddChildComponentsUsingGenericParam()
|
public void CanAddChildComponentsUsingGenericParam()
|
||||||
{
|
{
|
||||||
|
|
@ -370,6 +399,184 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
frame => AssertFrame.Text(frame, "Some text", 2));
|
frame => AssertFrame.Text(frame, "Some text", 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanAddElementReferenceCaptureInsideElement()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||||
|
Action<ElementRef> referenceCaptureAction = elementRef => { };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.OpenElement(0, "myelement"); // 0: <myelement
|
||||||
|
builder.AddAttribute(1, "attribute2", 123); // 1: attribute2=intExpression123>
|
||||||
|
builder.AddElementReferenceCapture(2, referenceCaptureAction); // 2: # capture: referenceCaptureAction
|
||||||
|
builder.AddContent(3, "some text"); // 3: some text
|
||||||
|
builder.CloseElement(); // </myelement>
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(builder.GetFrames(),
|
||||||
|
frame => AssertFrame.Element(frame, "myelement", 4, 0),
|
||||||
|
frame => AssertFrame.Attribute(frame, "attribute2", "123", 1),
|
||||||
|
frame => AssertFrame.ElementReferenceCapture(frame, referenceCaptureAction, 2),
|
||||||
|
frame => AssertFrame.Text(frame, "some text", 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CannotAddElementReferenceCaptureWithNoParent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
builder.AddElementReferenceCapture(0, _ => { });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CannotAddElementReferenceCaptureInsideComponent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
builder.OpenComponent<TestComponent>(0);
|
||||||
|
builder.AddElementReferenceCapture(1, _ => { });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CannotAddElementReferenceCaptureInsideRegion()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
builder.OpenRegion(0);
|
||||||
|
builder.AddElementReferenceCapture(1, _ => { });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanAddMultipleReferenceCapturesToSameElement()
|
||||||
|
{
|
||||||
|
// There won't be any way of doing this from Razor because there's no known use
|
||||||
|
// case for it. However it's harder to *not* support it than to support it, and
|
||||||
|
// there's no known reason to prevent it, so here's test coverage to show it
|
||||||
|
// just works.
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||||
|
Action<ElementRef> referenceCaptureAction1 = elementRef => { };
|
||||||
|
Action<ElementRef> referenceCaptureAction2 = elementRef => { };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.OpenElement(0, "myelement");
|
||||||
|
builder.AddElementReferenceCapture(0, referenceCaptureAction1);
|
||||||
|
builder.AddElementReferenceCapture(0, referenceCaptureAction2);
|
||||||
|
builder.CloseElement();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(builder.GetFrames(),
|
||||||
|
frame => AssertFrame.Element(frame, "myelement", 3),
|
||||||
|
frame => AssertFrame.ElementReferenceCapture(frame, referenceCaptureAction1),
|
||||||
|
frame => AssertFrame.ElementReferenceCapture(frame, referenceCaptureAction2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanAddComponentReferenceCaptureInsideComponent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||||
|
Action<object> myAction = elementRef => { };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.OpenComponent<TestComponent>(0); // 0: <TestComponent
|
||||||
|
builder.AddAttribute(1, "attribute2", 123); // 1: attribute2=intExpression123>
|
||||||
|
builder.AddComponentReferenceCapture(2, myAction); // 2: # capture: myAction
|
||||||
|
builder.AddContent(3, "some text"); // 3: some text
|
||||||
|
builder.CloseComponent(); // </TestComponent>
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(builder.GetFrames(),
|
||||||
|
frame => AssertFrame.Component<TestComponent>(frame, 4, 0),
|
||||||
|
frame => AssertFrame.Attribute(frame, "attribute2", 123, 1),
|
||||||
|
frame => AssertFrame.ComponentReferenceCapture(frame, myAction, 2),
|
||||||
|
frame => AssertFrame.Text(frame, "some text", 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CannotAddComponentReferenceCaptureWithNoParent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
builder.AddComponentReferenceCapture(0, _ => { });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CannotAddComponentReferenceCaptureInsideElement()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
builder.OpenElement(0, "myelement");
|
||||||
|
builder.AddComponentReferenceCapture(1, _ => { });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CannotAddComponentReferenceCaptureInsideRegion()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||||
|
|
||||||
|
// Act/Assert
|
||||||
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
|
{
|
||||||
|
builder.OpenRegion(0);
|
||||||
|
builder.AddComponentReferenceCapture(1, _ => { });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanAddMultipleReferenceCapturesToSameComponent()
|
||||||
|
{
|
||||||
|
// There won't be any way of doing this from Razor because there's no known use
|
||||||
|
// case for it. However it's harder to *not* support it than to support it, and
|
||||||
|
// there's no known reason to prevent it, so here's test coverage to show it
|
||||||
|
// just works.
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var builder = new RenderTreeBuilder(new TestRenderer());
|
||||||
|
Action<object> referenceCaptureAction1 = elementRef => { };
|
||||||
|
Action<object> referenceCaptureAction2 = elementRef => { };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
builder.OpenComponent<TestComponent>(0);
|
||||||
|
builder.AddComponentReferenceCapture(0, referenceCaptureAction1);
|
||||||
|
builder.AddComponentReferenceCapture(0, referenceCaptureAction2);
|
||||||
|
builder.CloseComponent();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(builder.GetFrames(),
|
||||||
|
frame => AssertFrame.Component<TestComponent>(frame, 3),
|
||||||
|
frame => AssertFrame.ComponentReferenceCapture(frame, referenceCaptureAction1),
|
||||||
|
frame => AssertFrame.ComponentReferenceCapture(frame, referenceCaptureAction2));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanClear()
|
public void CanClear()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,11 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
=> new RenderFragment[]
|
=> new RenderFragment[]
|
||||||
{
|
{
|
||||||
builder => builder.AddContent(0, "Hello"),
|
builder => builder.AddContent(0, "Hello"),
|
||||||
builder => builder.OpenElement(0, "Some Element"),
|
builder =>
|
||||||
|
{
|
||||||
|
builder.OpenElement(0, "Some Element");
|
||||||
|
builder.CloseElement();
|
||||||
|
},
|
||||||
builder =>
|
builder =>
|
||||||
{
|
{
|
||||||
builder.OpenElement(0, "Some Element");
|
builder.OpenElement(0, "Some Element");
|
||||||
|
|
@ -1323,6 +1327,137 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
Assert.Equal(new[] { 0, 1 }, batchBuilder.ComponentDisposalQueue);
|
Assert.Equal(new[] { 0, 1 }, batchBuilder.ComponentDisposalQueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AssignsDistinctIdToNewElementReferenceCaptures()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
ElementRef ref1 = default, ref2 = default;
|
||||||
|
Action<ElementRef> capture1 = val => { ref1 = val; };
|
||||||
|
Action<ElementRef> capture2 = val => { ref2 = val; };
|
||||||
|
newTree.OpenElement(0, "My element");
|
||||||
|
newTree.AddElementReferenceCapture(1, capture1);
|
||||||
|
newTree.AddElementReferenceCapture(2, capture2);
|
||||||
|
newTree.CloseElement();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var (diff, referenceFrames) = GetSingleUpdatedComponent();
|
||||||
|
|
||||||
|
// Assert: Distinct nonzero IDs
|
||||||
|
Assert.NotEqual(0, ref1.Id);
|
||||||
|
Assert.NotEqual(0, ref2.Id);
|
||||||
|
Assert.NotEqual(ref1.Id, ref2.Id);
|
||||||
|
|
||||||
|
// Assert: Also specified in diff
|
||||||
|
Assert.Collection(diff.Edits, edit =>
|
||||||
|
{
|
||||||
|
AssertEdit(edit, RenderTreeEditType.PrependFrame, 0);
|
||||||
|
Assert.Equal(0, edit.ReferenceFrameIndex);
|
||||||
|
});
|
||||||
|
Assert.Collection(referenceFrames,
|
||||||
|
frame => AssertFrame.Element(frame, "My element", 3),
|
||||||
|
frame =>
|
||||||
|
{
|
||||||
|
AssertFrame.ElementReferenceCapture(frame, capture1);
|
||||||
|
Assert.Equal(ref1.Id, frame.ElementReferenceCaptureId);
|
||||||
|
},
|
||||||
|
frame =>
|
||||||
|
{
|
||||||
|
AssertFrame.ElementReferenceCapture(frame, capture2);
|
||||||
|
Assert.Equal(ref2.Id, frame.ElementReferenceCaptureId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PreservesIdsOnRetainedElementReferenceCaptures()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var refWriteCount = 0;
|
||||||
|
ElementRef ref1 = default;
|
||||||
|
Action<ElementRef> capture1 = val => { ref1 = val; refWriteCount++; };
|
||||||
|
oldTree.OpenElement(0, "My element");
|
||||||
|
oldTree.AddElementReferenceCapture(1, capture1);
|
||||||
|
oldTree.CloseElement();
|
||||||
|
newTree.OpenElement(0, "My element");
|
||||||
|
newTree.AddElementReferenceCapture(1, capture1);
|
||||||
|
newTree.CloseElement();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var (diff, referenceFrames) = GetSingleUpdatedComponent(initializeFromFrames: true);
|
||||||
|
|
||||||
|
// Assert: Did not invoke the capture action a second time
|
||||||
|
// Note: We're not preserving the ReferenceCaptureId on the actual RenderTreeFrames in the same
|
||||||
|
// way we do for event handler IDs, simply because there's no need to do so. We only do
|
||||||
|
// anything with ReferenceCaptureId when frames are first inserted into the document.
|
||||||
|
Assert.NotEqual(0, ref1.Id);
|
||||||
|
Assert.Equal(1, refWriteCount);
|
||||||
|
Assert.Empty(diff.Edits);
|
||||||
|
Assert.Empty(referenceFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvokesAssignerForComponentReferenceCapturesOnInsertion()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
FakeComponent capturedInstance1 = null, capturedInstance2 = null;
|
||||||
|
Action<object> assigner1 = val => { capturedInstance1 = (FakeComponent)val; };
|
||||||
|
Action<object> assigner2 = val => { capturedInstance2 = (FakeComponent)val; };
|
||||||
|
newTree.OpenComponent<FakeComponent>(0);
|
||||||
|
newTree.AddComponentReferenceCapture(1, assigner1);
|
||||||
|
newTree.AddComponentReferenceCapture(2, assigner2);
|
||||||
|
newTree.CloseComponent();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var (diff, referenceFrames) = GetSingleUpdatedComponent();
|
||||||
|
|
||||||
|
// Assert: Assigned references
|
||||||
|
Assert.NotNull(capturedInstance1);
|
||||||
|
Assert.NotNull(capturedInstance2);
|
||||||
|
Assert.IsType<FakeComponent>(capturedInstance1);
|
||||||
|
Assert.IsType<FakeComponent>(capturedInstance2);
|
||||||
|
Assert.Same(capturedInstance1, capturedInstance2);
|
||||||
|
|
||||||
|
// Assert: Also in diff, even though we have no use for it there
|
||||||
|
// (it would be costly to exclude given how the array range is copied)
|
||||||
|
Assert.Collection(diff.Edits, edit =>
|
||||||
|
{
|
||||||
|
AssertEdit(edit, RenderTreeEditType.PrependFrame, 0);
|
||||||
|
Assert.Equal(0, edit.ReferenceFrameIndex);
|
||||||
|
});
|
||||||
|
Assert.Collection(referenceFrames,
|
||||||
|
frame =>
|
||||||
|
{
|
||||||
|
AssertFrame.Component<FakeComponent>(frame, 3, 0);
|
||||||
|
Assert.Same(capturedInstance1, frame.Component);
|
||||||
|
},
|
||||||
|
frame => AssertFrame.ComponentReferenceCapture(frame, assigner1, 1),
|
||||||
|
frame => AssertFrame.ComponentReferenceCapture(frame, assigner2, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DoesNotInvokeAssignerAgainForRetainedComponents()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var refWriteCount = 0;
|
||||||
|
FakeComponent capturedInstance = null;
|
||||||
|
Action<object> assigner = val => { capturedInstance = (FakeComponent)val; refWriteCount++; };
|
||||||
|
oldTree.OpenComponent<FakeComponent>(0);
|
||||||
|
oldTree.AddComponentReferenceCapture(1, assigner);
|
||||||
|
oldTree.CloseComponent();
|
||||||
|
newTree.OpenComponent<FakeComponent>(0);
|
||||||
|
newTree.AddComponentReferenceCapture(1, assigner);
|
||||||
|
newTree.CloseComponent();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var (diff, referenceFrames) = GetSingleUpdatedComponent(initializeFromFrames: true);
|
||||||
|
|
||||||
|
// Assert: Did not invoke the capture action a second time
|
||||||
|
Assert.NotNull(capturedInstance);
|
||||||
|
Assert.IsType<FakeComponent>(capturedInstance);
|
||||||
|
Assert.Equal(1, refWriteCount);
|
||||||
|
Assert.Empty(diff.Edits);
|
||||||
|
Assert.Empty(referenceFrames);
|
||||||
|
}
|
||||||
|
|
||||||
private (RenderTreeDiff, RenderTreeFrame[]) GetSingleUpdatedComponent(bool initializeFromFrames = false)
|
private (RenderTreeDiff, RenderTreeFrame[]) GetSingleUpdatedComponent(bool initializeFromFrames = false)
|
||||||
{
|
{
|
||||||
var result = GetSingleUpdatedComponentWithBatch(initializeFromFrames);
|
var result = GetSingleUpdatedComponentWithBatch(initializeFromFrames);
|
||||||
|
|
|
||||||
|
|
@ -950,6 +950,47 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
||||||
&& edit.RemovedAttributeName == "disabled");
|
&& edit.RemovedAttributeName == "disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HandlesNestedElementCapturesDuringRefresh()
|
||||||
|
{
|
||||||
|
// This may seem like a very arbitrary test case, but at once stage there was a bug
|
||||||
|
// whereby the diff output was incorrect given a ref capture on an element whose
|
||||||
|
// parent element also had a ref capture
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
var attrValue = 0;
|
||||||
|
var component = new TestComponent(builder =>
|
||||||
|
{
|
||||||
|
builder.OpenElement(0, "parent elem");
|
||||||
|
builder.AddAttribute(1, "parent elem attr", attrValue);
|
||||||
|
builder.AddElementReferenceCapture(2, _ => { });
|
||||||
|
builder.OpenElement(3, "child elem");
|
||||||
|
builder.AddElementReferenceCapture(4, _ => { });
|
||||||
|
builder.AddContent(5, "child text");
|
||||||
|
builder.CloseElement();
|
||||||
|
builder.CloseElement();
|
||||||
|
});
|
||||||
|
var renderer = new TestRenderer();
|
||||||
|
renderer.AssignComponentId(component);
|
||||||
|
|
||||||
|
// Act: Update the attribute value on the parent
|
||||||
|
component.TriggerRender();
|
||||||
|
attrValue++;
|
||||||
|
component.TriggerRender();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var latestBatch = renderer.Batches.Skip(1).Single();
|
||||||
|
var latestDiff = latestBatch.DiffsInOrder.Single();
|
||||||
|
Assert.Collection(latestDiff.Edits,
|
||||||
|
edit =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeEditType.SetAttribute, edit.Type);
|
||||||
|
Assert.Equal(0, edit.SiblingIndex);
|
||||||
|
AssertFrame.Attribute(latestBatch.ReferenceFrames[edit.ReferenceFrameIndex],
|
||||||
|
"parent elem attr", 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private class NoOpRenderer : Renderer
|
private class NoOpRenderer : Renderer
|
||||||
{
|
{
|
||||||
public NoOpRenderer() : base(new TestServiceProvider())
|
public NoOpRenderer() : base(new TestServiceProvider())
|
||||||
|
|
|
||||||
|
|
@ -107,5 +107,19 @@ namespace Microsoft.AspNetCore.Blazor.Test.Helpers
|
||||||
AssertFrame.Sequence(frame, sequence);
|
AssertFrame.Sequence(frame, sequence);
|
||||||
Assert.True(string.IsNullOrWhiteSpace(frame.TextContent));
|
Assert.True(string.IsNullOrWhiteSpace(frame.TextContent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ElementReferenceCapture(RenderTreeFrame frame, Action<ElementRef> action, int? sequence = null)
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeFrameType.ElementReferenceCapture, frame.FrameType);
|
||||||
|
Assert.Same(action, frame.ElementReferenceCaptureAction);
|
||||||
|
AssertFrame.Sequence(frame, sequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ComponentReferenceCapture(RenderTreeFrame frame, Action<object> action, int? sequence = null)
|
||||||
|
{
|
||||||
|
Assert.Equal(RenderTreeFrameType.ComponentReferenceCapture, frame.FrameType);
|
||||||
|
Assert.Same(action, frame.ComponentReferenceCaptureAction);
|
||||||
|
AssertFrame.Sequence(frame, sequence);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
@using Microsoft.AspNetCore.Blazor
|
||||||
|
|
||||||
|
<h1>Component capture</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This shows how a component reference may be captured as a field value using 'ref' syntax.
|
||||||
|
This feature is intended only for cases where you're triggering an action on the child
|
||||||
|
instance. It should <strong>not</strong> be used as a way of mutating state in the child,
|
||||||
|
because that would bypass all the benefits of flowing parameters to children and re-rendering
|
||||||
|
automatically only when required.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@if (_toggleCapturedComponentPresence)
|
||||||
|
{
|
||||||
|
<div id="child-component">
|
||||||
|
<CounterComponent ref="_myChildCounter" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>External controls</legend>
|
||||||
|
<button id="reset-child" onclick="@ResetChildCounter">Reset</button>
|
||||||
|
<label>
|
||||||
|
<input id="toggle-child" type="checkbox" bind="_toggleCapturedComponentPresence" />
|
||||||
|
Toggle counter presence
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
bool _toggleCapturedComponentPresence = true;
|
||||||
|
CounterComponent _myChildCounter;
|
||||||
|
|
||||||
|
void ResetChildCounter()
|
||||||
|
{
|
||||||
|
_myChildCounter.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,4 +15,10 @@
|
||||||
{
|
{
|
||||||
currentCount++;
|
currentCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
currentCount = 0;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
@using Microsoft.AspNetCore.Blazor
|
||||||
|
@using Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||||
|
|
||||||
|
<h1>Element capture</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This shows how an element reference may be captured as a field value using 'ref' syntax and then
|
||||||
|
passed to JavaScript code, which receives the actual DOM element instance.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Note that 'ref' syntax is primarily intended for use with JavaScript interop. It is <strong>not</strong>
|
||||||
|
recommended to use it for mutating the DOM routinely. All DOM construction and mutation that can be
|
||||||
|
done declaratively is better, as it automatically happens at the correct time and with minimal diffs.
|
||||||
|
Plus, whenever you use 'ref', you will not be able to run the same code during unit tests or
|
||||||
|
server-side rendering. So it's always better to prefer declarative UI construction when possible.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@if (_toggleCapturedElementPresence)
|
||||||
|
{
|
||||||
|
<input id="capturedElement" ref="_myInput" />
|
||||||
|
}
|
||||||
|
<button onclick="@MakeInteropCall">Click me</button>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" bind="_toggleCapturedElementPresence" />
|
||||||
|
Toggle input
|
||||||
|
</label>
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
int _count = 0;
|
||||||
|
bool _toggleCapturedElementPresence = true;
|
||||||
|
ElementRef _myInput;
|
||||||
|
|
||||||
|
void MakeInteropCall()
|
||||||
|
{
|
||||||
|
RegisteredFunction.Invoke<object>("setElementValue", _myInput, $"Clicks: {++_count}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,8 @@
|
||||||
<option value="BasicTestApp.SvgComponent">SVG</option>
|
<option value="BasicTestApp.SvgComponent">SVG</option>
|
||||||
<option value="BasicTestApp.SvgWithChildComponent">SVG with child component</option>
|
<option value="BasicTestApp.SvgWithChildComponent">SVG with child component</option>
|
||||||
<option value="BasicTestApp.LogicalElementInsertionCases">Logical element insertion cases</option>
|
<option value="BasicTestApp.LogicalElementInsertionCases">Logical element insertion cases</option>
|
||||||
|
<option value="BasicTestApp.ElementRefComponent">Element ref component</option>
|
||||||
|
<option value="BasicTestApp.ComponentRefComponent">Component ref component</option>
|
||||||
<!--<option value="BasicTestApp.RouterTest.Default">Router</option> Excluded because it requires additional setup to work correctly when loaded manually -->
|
<!--<option value="BasicTestApp.RouterTest.Default">Router</option> Excluded because it requires additional setup to work correctly when loaded manually -->
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|
@ -54,6 +56,11 @@
|
||||||
var method = Blazor.platform.findMethod('BasicTestApp', 'BasicTestApp', 'Program', 'MountTestComponent');
|
var method = Blazor.platform.findMethod('BasicTestApp', 'BasicTestApp', 'Program', 'MountTestComponent');
|
||||||
Blazor.platform.callMethod(method, null, [Blazor.platform.toDotNetString(typeName)]);
|
Blazor.platform.callMethod(method, null, [Blazor.platform.toDotNetString(typeName)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used by ElementRefComponent
|
||||||
|
Blazor.registerFunction('setElementValue', function (element, newValue) {
|
||||||
|
element.value = newValue;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue