Skip to content
Merged
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ For a migration guide, see [Upgrading bUnit](https://bunit.dev/docs/migrations/i

### Added

- Support for form submission from submit buttons and inputs that are outside the form element but associated via the HTML5 `form` attribute. Reported and fixed in [#1766](https://github.com/bUnit-dev/bUnit/issues/1766).
- Improved renderer logic that catches more edge cases.
- Improved developer experience in relation to JSInterop.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,56 @@ private static bool TryGetParentFormElementSpecialCase(
IHtmlButtonElement { Type: "submit", Form: not null } button => button.Form,
_ => null
};

// If form is still null, try to find it by the form attribute for submit buttons/inputs
if (form is null && element.HasAttribute("form"))
{
var isSubmitElement = element switch
{
IHtmlInputElement { Type: "submit" } => true,
IHtmlButtonElement { Type: "submit" } => true,
_ => false
};

if (isSubmitElement)
{
var formId = element.GetAttribute("form");
if (!string.IsNullOrEmpty(formId))
{
// Try to find the form element by traversing up to find a common ancestor
// and then searching down for the form
form = FindFormById(element, formId);
}
}
}

return form is not null
&& form.TryGetEventId(Htmlizer.ToBlazorAttribute("onsubmit"), out eventId);
}

private static IHtmlFormElement? FindFormById(IElement element, string formId)
{
// Traverse up the DOM tree to find a common ancestor and search its children
// for the form with the matching ID. This handles cases where the button and
// form are siblings or in different subtrees.
var current = element.Parent as IElement;
while (current is not null)
{
// Search children of current element for the form with matching ID
foreach (var child in current.Children)
{
if ((child.Id == formId || child.GetAttribute("id") == formId) && child is IHtmlFormElement htmlForm)
{
return htmlForm;
}
}

// Move up to parent to widen the search
current = current.Parent as IElement;
}

return null;
}

private static bool EventIsDisabled(this IElement element, string eventName)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<form @onsubmit="Callback" id="form1">
</form>
<button type="submit" form="form1" id="submitter">Submit</button>

@code {
public bool SubmitWasCalled { get; private set; }

private void Callback()
{
SubmitWasCalled = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<form @onsubmit="Callback" id="form1">
</form>
<button type="submit" form="form1" id="submit-button">Submit Button</button>
<input type="submit" form="form1" id="submit-input" value="Submit Input" />

@code {
public bool SubmitWasCalled { get; private set; }

private void Callback()
{
SubmitWasCalled = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,59 @@ public class FormDispatchExtensionTest : BunitContext
public void ClickingOnSubmitButtonTriggersOnsubmitOfForm()
{
var cut = Render<FormWithButton>(p => p.Add(s => s.HasSubmitType, true));

cut.Find("#submitter").Click();

cut.Instance.SubmitWasCalled.ShouldBeTrue();
}

[Fact]
public void ButtonWithoutTypeIsConsideredSubmitAndTriggersOnsubmitOfForm()
{
var cut = Render<FormWithButton>(p => p.Add(s => s.HasSubmitType, false));

cut.Find("#submitter").Click();

cut.Instance.SubmitWasCalled.ShouldBeTrue();
}

[Fact]
public void ButtonThatIsNotSubmitShouldNotTrigger()
{
var cut = Render<FormWithButton>(p => p.Add(s => s.HasSubmitType, false));

cut.Find("#other").Click();

cut.Instance.SubmitWasCalled.ShouldBeFalse();
}

[Fact]
public void ClickingOnSubmitButtonOutsideTriggersOnsubmitOfForm()
{
var cut = Render<FormWithButtonOutside>();

cut.Find("#submitter").Click();

cut.Instance.SubmitWasCalled.ShouldBeTrue();
}

[Fact]
public void ClickingOnSubmitButtonOutsideWithFormAttributeTriggersOnsubmit()
{
var cut = Render<FormWithSubmitElementsOutside>();

cut.Find("#submit-button").Click();

cut.Instance.SubmitWasCalled.ShouldBeTrue();
}

[Fact]
public void ClickingOnSubmitInputOutsideWithFormAttributeTriggersOnsubmit()
{
var cut = Render<FormWithSubmitElementsOutside>();

cut.Find("#submit-input").Click();

cut.Instance.SubmitWasCalled.ShouldBeTrue();
}
}