Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add support for ElementReference.FocusAsync from .NET 5 #231

Closed
Jarek300 opened this issue Oct 12, 2020 · 9 comments · Fixed by #260
Closed

Add support for ElementReference.FocusAsync from .NET 5 #231

Jarek300 opened this issue Oct 12, 2020 · 9 comments · Fixed by #260
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@Jarek300
Copy link

How to render component that uses function ElementReference.FocusAsync (in .NET 5)? When I try to render the component in test, exception is thrown:

System.AggregateException : One or more errors occurred. (ElementReference has not been configured correctly.)
----> System.InvalidOperationException : ElementReference has not been configured correctly.

Component creates ElementReference to input tag and in function OnAfterRenderAsync calls FocusAsync.

ElementReference mInputVariableName;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        await mInputVariableName.FocusAsync();
        return;
    }
}
@Jarek300 Jarek300 added the question Further information is requested label Oct 12, 2020
@egil
Copy link
Member

egil commented Oct 12, 2020

Hi @Jarek300

Can you please share a minimal but complete example of a component causes this, and also the test code you have that tries to render it. You might have stumpled upon something that is currently unsupported by bUnit. If that is the case, we need to add support.

Thanks.

@Jarek300
Copy link
Author

I attach test solution. The test project contains one test TestMyInput.

TestAutoFocus.zip

@egil
Copy link
Member

egil commented Oct 13, 2020

Took a look at this @Jarek300, and it seems we are hitting this: https://github.com/dotnet/aspnetcore/blob/master/src/Components/Web/src/ElementReferenceExtensions.cs#L36

The problem seem to be that the ElementReference's Context property is null. How bUnit can set that I will need to ask the Blazor team about (i.e. hoping that one of @SteveSandersonMS, @captainsafia, or @danroth27 can help).

I have tried to register WebElementReferenceContext in the DI container, i.e. ctx.Services.AddSingleton<ElementReferenceContext, WebElementReferenceContext>();, but that does not seem to help.

Additional info:

Here is your example even more distilled, but with IJSRuntime injected into it, to show that one is indeed available to the component:

// MyInput.razor
@inject IJSRuntime JSRuntime

<input @ref="elmRef" />

@code 
{
    ElementReference elmRef;
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await elmRef.FocusAsync();
        }
    }
}

and the test:

// MyInputTest.cs
using Bunit.TestDoubles.JSInterop;
using BUnitTestAutoFocus;
using NUnit.Framework;

public class Tests
{
    [Test]
    public void TestMyInput()
    {
        using var ctx = new Bunit.TestContext();
        ctx.Services.AddMockJSRuntime(JSRuntimeMockMode.Strict);

        var cut = ctx.RenderComponent<MyInput>();
    }
}

And here is the screenshot of the state of the component and elmRef ElementReference variable when the component is rendered:

image

@egil egil added enhancement New feature or request help wanted Extra attention is needed and removed question Further information is requested labels Oct 13, 2020
@egil egil changed the title How to render component that uses function ElementReference.FocusAsync from .NET 5? Add support for ElementReference.FocusAsync from .NET 5 Oct 13, 2020
@egil egil added the .net5 label Oct 13, 2020
@SteveSandersonMS
Copy link

The ElementReferenceContext is passed in from the Renderer's ElementReferenceContext property. Your subclass of Renderer can populate its own ElementReferenceContext property.

@egil
Copy link
Member

egil commented Oct 13, 2020

Thanks @SteveSandersonMS, that seems easy enough.

@Jarek300, with what Steve suggests, it will be possible for me to ensure that the component under test renders without a problem. However, you will not be able to observe the "focusness" in the rendered markup, since it is a pseudo state in browsers.

There are various ways bUnit can expose that the call has happened, and what element was the target. Is that relevant to you from a testing point of view?

@Jarek300
Copy link
Author

For now it is enough if calling FocusAsync under the test does not throw an exception. Observing the "focusness" in the rendered markup is not needed.

My application contains many 'modal dialogs' and checking if the right element was focused would be useful in the future. It up to you how bUnit could it expose.

@Codibex
Copy link

Codibex commented Nov 13, 2020

I had the same problem in the partial class.
I have changed your code

ElementReference elmRef;
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await elmRef.FocusAsync();
        }
    }

to

ElementReference elmRef;
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender)
        {
            await elmRef.FocusAsync();
        }
    }

because the elmRef was not set in the first run. The !firstRender solved the problem.

@egil
Copy link
Member

egil commented Nov 20, 2020

@Jarek300, I have landed on a solution that works like this, from a test users perspective. What do you think?

[Fact(DisplayName = "Can render components that calls FocusAsync")]
public void Test001()
{
	var cut = RenderComponent<FocusingComponent>();
	var input = cut.Find("input");
	JSInterop.VerifyFocusAsyncInvoke().Arguments[0].ShouldBeElementReferenceTo(input);
}

class FocusingComponent : ComponentBase
{
	ElementReference elmRef;
	protected override async Task OnAfterRenderAsync(bool firstRender)
	{
		if (firstRender)
		{
			await elmRef.FocusAsync();
		}
	}

	protected override void BuildRenderTree(RenderTreeBuilder builder)
	{
		builder.OpenElement(1, "input");
		builder.AddElementReferenceCapture(2, x => elmRef = x);
		builder.CloseElement();
	}
}

@egil egil mentioned this issue Nov 20, 2020
9 tasks
@egil egil linked a pull request Nov 20, 2020 that will close this issue
9 tasks
@Jarek300
Copy link
Author

Looks good to me.

@egil egil closed this as completed Nov 22, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants