Repository: dotnet/aspnetcore
File: src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/test/GridRaceConditionTest.cs (C#)

Code before:

    protected void BuildColumnsRenderFragment(RenderTreeBuilder builder)
    {
        //Render the PropertyColumn for Id
        builder.OpenComponent<PropertyColumn<Person, int>>(0);
        builder.AddAttribute(1, nameof(PropertyColumn<Person, int>.Property),
            (System.Linq.Expressions.Expression<Func<Person, int>>)(p => p.Id));
        builder.AddAttribute(2, nameof(PropertyColumn<Person, int>.Sortable), true);
        builder.CloseComponent();

        //Render the PropertyColumn for Name
        builder.OpenComponent<PropertyColumn<Person, string>>(3);
        builder.AddAttribute(4, nameof(PropertyColumn<Person, string>.Property),
            (System.Linq.Expressions.Expression<Func<Person, string>>)(p => p.Name));
        builder.AddAttribute(5, nameof(PropertyColumn<Person, string>.Sortable), true);
        builder.CloseComponent();

        //Render the PropertyColumn for Age
        builder.OpenComponent<PropertyColumn<Person, int>>(6);
        builder.AddAttribute(7, nameof(PropertyColumn<Person, int>.Property),
            (System.Linq.Expressions.Expression<Func<Person, int>>)(p => p.Age));
        builder.AddAttribute(8, nameof(PropertyColumn<Person, int>.Sortable), true);
        builder.CloseComponent();
    }

    public void TriggerRender()
    {
        InvokeAsync(StateHasChanged);
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await Task.Delay(1);
            StateHasChanged();
        }
        await base.OnAfterRenderAsync(firstRender);
    }
}

internal class TestJsRuntime(TaskCompletionSource tcs) : IJSRuntime
{
    private readonly TaskCompletionSource _tcs = tcs;

    public async ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args = null)
    {
        if (identifier == "import" && args != null && args.Length > 0 && args[0] is string modulePath)
        {
            if (modulePath == "./_content/Microsoft.AspNetCore.Components.QuickGrid/QuickGrid.razor.js")
            {

Review comment (bug):
When is `TriggerRender` called? Let's remove all the changes that are not directly connected to testing the bug.

Code after:
        moduleLoadCompletion.SetResult();

        // Wait until after OnAfterRenderAsync has completed, to make sure jsmodule import started and the reported issue is reproduced
        var failingGrid = testComponent.FailingQuickGrid;
        await failingGrid.OnAfterRenderCompleted;

        // Assert that init WAS called after disposal
        // The FailingQuickGrid's OnAfterRenderAsync should have called init despite being disposed
        // The FailingQuickGrid should not have disposed of JsModule 
        Assert.True(testJsRuntime.InitWasCalledAfterDisposal,
            $"FailingQuickGrid should call init after disposal, demonstrating the race condition bug. " +
            $"InitWasCalledAfterDisposal: {testJsRuntime.InitWasCalledAfterDisposal}, " +
            $"DisposeAsyncWasCalled: {failingGrid.DisposeAsyncWasCalled}, " +
            $"_disposeBool is false: {failingGrid.IsWasDisposedFalse()}");
        Assert.False(testJsRuntime.JsModuleDisposed);
    }
}

internal class Person
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

internal abstract class BaseTestComponent<TGrid> : ComponentBase
    where TGrid : ComponentBase
{
    [Inject] public IJSRuntime JSRuntime { get; set; } = default!;

    protected TGrid _grid;
    public TGrid Grid => _grid;

    private readonly List<Person> _people = [
        new() { Id = 1, Name = "John" },
        new() { Id = 2, Name = "Jane" }
    ];

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        builder.OpenComponent<TGrid>(0);
        builder.AddAttribute(1, "Items", _people.AsQueryable());
        builder.AddAttribute(2, "ChildContent", (RenderFragment)(b =>
        {
            b.OpenComponent<PropertyColumn<Person, int>>(0);
            b.AddAttribute(1, "Property", (System.Linq.Expressions.Expression<Func<Person, int>>)(p => p.Id));
            b.CloseComponent();
        }));
        builder.AddComponentReferenceCapture(3, component => _grid = (TGrid)component);
        builder.CloseComponent();
    }
}