Skip to content

Add unit tests for link tag helpers#2197

Merged
adelikat merged 4 commits intoTASVideos:mainfrom
YoshiRulz:tag-helper-test
Mar 18, 2026
Merged

Add unit tests for link tag helpers#2197
adelikat merged 4 commits intoTASVideos:mainfrom
YoshiRulz:tag-helper-test

Conversation

@YoshiRulz
Copy link
Copy Markdown
Collaborator

@YoshiRulz YoshiRulz commented Aug 18, 2025

Testing these:

public class PubLinkTagHelper : TagHelper
{
public int Id { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "a";
output.Attributes.Add("href", $"/{Id}M");
}
}
public class SubLinkTagHelper : TagHelper
{
public int Id { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "a";
output.Attributes.Add("href", $"/{Id}S");
}
}
public class GameLinkTagHelper : TagHelper
{
public int Id { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "a";
output.Attributes.Add("href", $"/{Id}G");
}
}
public class WikiLinkTagHelper : TagHelper
{
public string PageName { get; set; } = "";
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var pageName = PageName.Trim('/');
var submissionId = SubmissionHelper.IsRawSubmissionLink(PageName);
if (submissionId.HasValue)
{
pageName = $"{submissionId}S";
}
var publicationId = SubmissionHelper.IsRawPublicationLink(PageName);
if (publicationId.HasValue)
{
pageName = $"{publicationId}M";
}
var gameId = SubmissionHelper.IsRawGamePageLink(PageName);
if (gameId.HasValue)
{
pageName = $"{gameId}G";
}
output.TagName = "a";
output.Attributes.Add("href", $"/{pageName}");
output.Content.Clear();
output.Content.AppendHtml(pageName);
}
}
public class ProfileLinkTagHelper(IHtmlGenerator htmlGenerator) : AnchorTagHelper(htmlGenerator)
{
public string? Username { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var innerContent = await output.GetChildContentAsync();
if (innerContent.IsEmptyOrWhiteSpace)
{
output.Content.Clear();
output.Content.Append(Username ?? "");
}
output.TagName = "a";
Page = "/Users/Profile";
RouteValues.Add("UserName", Username);
await base.ProcessAsync(context, output);
}
}

@YoshiRulz YoshiRulz force-pushed the tag-helper-test branch 2 times, most recently from c82ac66 to 32b81f7 Compare August 24, 2025 19:52
@YoshiRulz

This comment was marked as outdated.

@Masterjun3
Copy link
Copy Markdown
Collaborator

Masterjun3 commented Sep 4, 2025

So basically, if you want to use Page, you actually have to give it a page that exists. For example /123M is not a page that exists. It's actually /Publications/View and we only reroute it like
options.Conventions.AddPageRoute("/Publications/View", "{id:int}M").

Also the output TagName needs to be set to a, otherwise it stays at the original one like pub-link.
For example:

--- a/TASVideos/TagHelpers/WikiLinkTagHelpers.cs
+++ b/TASVideos/TagHelpers/WikiLinkTagHelpers.cs
@@ -11,7 +11,9 @@ public class PubLinkTagHelper(IHtmlGenerator generator) : AnchorTagHelper(genera

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
-               Page = $"/{Id}M";
+               output.TagName = "a";
+               Page = "/Publications/View";
+               RouteValues.Add("Id", Id.ToString());
                base.Process(context, output);
        }
 }

Though I'm wondering why we don't just set the href directly with output.Attributes.Add("href", $"/{Id}M"); instead of using Page, like it was done before this PR?

@YoshiRulz
Copy link
Copy Markdown
Collaborator Author

YoshiRulz commented Sep 4, 2025

Thank you, that was the small detail I was missing. And since it doesn't work for wiki pages, I've reverted the change to <wiki-link/>, but the rest are now working.

Though I'm wondering why we don't just set the href directly with output.Attributes.Add("href", $"/{Id}M"); instead of using Page, like it was done before this PR?

The idea was to use the same mechanism as <a asp-page/>. After this PR I wanted to add more link helpers, but existing calls mostly use that rather than <a href/>. Also, I'm hoping that <a asp-page/> would help with tracking page links (for generating referrer lists).

@YoshiRulz YoshiRulz marked this pull request as ready for review September 4, 2025 23:35
public class AddLinkTagHelper(IHtmlGenerator generator) : AnchorTagHelper(generator)
{
public override void Process(TagHelperContext context, TagHelperOutput output)
=> ProcessAsync(context, output).Wait();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is bad. Why are we doing this? .Wait() on async calls is asking for deadlocks

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first commit is to fix an inconsistency in subclasses of AnchorTagHelper. In TagHelper, ProcessAsync calls Process, which is fine. AnchorTagHelper overrides Process only, which is fine. But those subclasses were overriding ProcessAsync only, so if Process is called (which I guess was never happening, thankfully) then it would be the implementation from AnchorTagHelper, which is obviously not going to produce the same output.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not implement both Process and ProcessAsync? The .Wait() is a no go for me, need another way

{
output.TagName = "a";
await base.ProcessAsync(context, output);
base.Process(context, output);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, why are we not doing async here?

Copy link
Copy Markdown
Collaborator Author

@YoshiRulz YoshiRulz Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AnchorTagHelper doesn't override ProcessAsync, so that would actually be the implementation from TagHelper, which makes a virtual call to this.Process, which calls this method recursively.

@YoshiRulz
Copy link
Copy Markdown
Collaborator Author

Moved that to a separate PR, and rewrote the tests here to be async.

namespace TASVideos.TagHelpers;

public class PubLinkTagHelper : TagHelper
public class PubLinkTagHelper(IHtmlGenerator generator) : AnchorTagHelper(generator)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this being used?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes?


Do you mean the constructor parameter? It's being passed through to the base class.

{
output.TagName = "a";
output.Attributes.Add("href", $"/{Id}M");
Page = "/Publications/View";
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wyy make this change? does it change any behaviors?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My earlier comment:

The idea was to use the same mechanism as <a asp-page/>. After this PR I wanted to add more link helpers, but existing calls mostly use that rather than <a href/>. Also, I'm hoping that <a asp-page/> would help with tracking page links (for generating referrer lists).

The unit tests should catch regressions, but I also did a bit of manual testing.

if (virtualPath is not null)
{
// not sure why, but the inner Route adds the whole path again as `?path=`, so strip that
// TODO hmm, the queryparam reflects AnchorTagHelper.Page, while the actual path reflects the route template, meaning this is discarding info that should be asserted against
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@YoshiRulz Are you going to do this TODO? We want complete PRs, not ones with work left to do.

Copy link
Copy Markdown
Collaborator Author

@YoshiRulz YoshiRulz Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've honestly forgotten what this was about, but based on my comment, the opportunity here is that there's a second string which could theoretically be exfiltrated to the calling unit test. I'm not sure how to do that given the architecture.

I believe this PR is complete modulo a merge conflict, just pending adelikat's review.

@adelikat adelikat merged commit cad7ee4 into TASVideos:main Mar 18, 2026
1 check passed
@YoshiRulz YoshiRulz deleted the tag-helper-test branch March 18, 2026 23:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants