Skip to content

Fix ScriptTagHelper regression by checking for existing content before processing importmaps #63155

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

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Mvc/Mvc.TagHelpers/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
~override Microsoft.AspNetCore.Mvc.TagHelpers.ScriptTagHelper.ProcessAsync(Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext context, Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput output) -> System.Threading.Tasks.Task
17 changes: 15 additions & 2 deletions src/Mvc/Mvc.TagHelpers/src/ScriptTagHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,14 +236,27 @@ private StringWriter StringWriter

/// <inheritdoc />
public override void Process(TagHelperContext context, TagHelperOutput output)
{
ProcessAsync(context, output).GetAwaiter().GetResult();
}

/// <inheritdoc />
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(output);

if (string.Equals(Type, "importmap", StringComparison.OrdinalIgnoreCase))
{
// This is an importmap script, we'll write out the import map and
// stop processing.
// This is an importmap script, check if there's existing content first
var childContent = await output.GetChildContentAsync();
if (!childContent.IsEmptyOrWhiteSpace)
{
// User provided explicit content, preserve it
return;
}

// No existing content, so we can apply import map logic
var importMap = ImportMap ?? ViewContext.HttpContext.GetEndpoint()?.Metadata.GetMetadata<ImportMapDefinition>();
if (importMap == null)
{
Expand Down
54 changes: 54 additions & 0 deletions src/Mvc/Mvc.TagHelpers/test/ScriptTagHelperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,60 @@ public void ScriptTagHelper_RendersImportMap_FromEndpoint()
Assert.Equal(importMap.ToJson(), output.Content.GetContent());
}

[Fact]
public async Task ScriptTagHelper_PreservesExplicitImportMapContent_WhenUserProvidesContent()
{
// Arrange - this simulates the user's scenario where they provide explicit importmap content
var context = MakeTagHelperContext(
attributes: new TagHelperAttributeList
{
new TagHelperAttribute("type", "importmap"),
});

var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
// Simulate user providing explicit content
output.Content.SetHtmlContent(@"{""imports"":{""jquery"":""https://code.jquery.com/jquery.js""}}");

var helper = GetHelper();
helper.Type = "importmap";
// No endpoint with ImportMapDefinition - this should NOT suppress the output
// since user provided explicit content

// Act
await helper.ProcessAsync(context, output);

// Assert
Assert.Equal("script", output.TagName); // Tag should not be suppressed
Assert.Equal("importmap", output.Attributes["type"].Value);
// The user's explicit content should be preserved
Assert.Equal(@"{""imports"":{""jquery"":""https://code.jquery.com/jquery.js""}}", output.Content.GetContent());
}

[Fact]
public async Task ScriptTagHelper_SuppressesOutput_WhenNoContentAndNoImportMapDefinition()
{
// Arrange - this simulates an empty importmap script with no definition
var context = MakeTagHelperContext(
attributes: new TagHelperAttributeList
{
new TagHelperAttribute("type", "importmap"),
});

var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList());
// No content provided

var helper = GetHelper();
helper.Type = "importmap";
// No endpoint with ImportMapDefinition and no explicit content
// This should suppress the output since there's nothing to render

// Act
await helper.ProcessAsync(context, output);

// Assert - output should be suppressed when no content and no definition
Assert.Null(output.TagName); // Tag should be suppressed
}

private Endpoint CreateEndpoint(ImportMapDefinition importMap = null)
{
return new Endpoint(
Expand Down
Loading