Skip to content

Commit c094d30

Browse files
Honor preventdefault for events so they don't bubble (#982)
* Honor preventdefault for events so they don't bubble * Small change to fix the normal bubbling events * Small rename of a private method to better indicate its intention * Improvement and simplification * Build fix by removing an empty statement * Changelog * formatting of the changelog * Updated changelog --------- Co-authored-by: Steven Giesel <[email protected]>
1 parent e361552 commit c094d30

File tree

4 files changed

+42
-17
lines changed

4 files changed

+42
-17
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ All notable changes to **bUnit** will be documented in this file. The project ad
66

77
## [Unreleased]
88

9+
- Submit buttons and input fields now no longer cause a form submit when they have the `@onclick:preventDefault` attribute. By [@JelleHissink](https://github.com/JelleHissink).
10+
911
## [1.16.2] - 2023-02-07
1012

1113
- Changed semantic comparer to handle elements parsed outside their proper context, e.g. an `<path>` element parsed without being inside a `<svg>` element. The semantic comparer will now be able to treat those as regular elements and thus be able to compare correctly to other elements of the same type and with the same node name. By [@egil](https://github.com/egil).

src/bunit.web/EventDispatchExtensions/TriggerEventDispatchExtensions.cs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -80,30 +80,39 @@ public static Task TriggerEventAsync(this IElement element, string eventName, Ev
8080
}
8181

8282
[SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "HTML events are standardize to lower case and safe in this context.")]
83-
private static Task TriggerEventsAsync(ITestRenderer renderer, IElement element, string eventName, EventArgs eventArgs)
83+
private static async Task TriggerEventsAsync(ITestRenderer renderer, IElement element, string eventName, EventArgs eventArgs)
8484
{
8585
var isNonBubblingEvent = NonBubblingEvents.Contains(eventName.ToLowerInvariant());
8686
var unwrappedElement = element.Unwrap();
8787
if (isNonBubblingEvent)
88-
return TriggerNonBubblingEventAsync(renderer, unwrappedElement, eventName, eventArgs);
88+
await TriggerNonBubblingEventAsync(renderer, unwrappedElement, eventName, eventArgs);
89+
else
90+
await TriggerBubblingEventAsync(renderer, unwrappedElement, eventName, eventArgs);
8991

90-
return unwrappedElement switch
92+
switch (unwrappedElement)
9193
{
92-
IHtmlInputElement { Type: "submit", Form: not null } input when eventName is "onclick"
93-
=> TriggerFormSubmitBubblingEventAsync(renderer, input, eventArgs, input.Form),
94-
IHtmlButtonElement { Type: "submit", Form: not null } button when eventName is "onclick"
95-
=> TriggerFormSubmitBubblingEventAsync(renderer, button, eventArgs, button.Form),
96-
_ => TriggerBubblingEventAsync(renderer, unwrappedElement, eventName, eventArgs)
97-
};
94+
case IHtmlInputElement { Type: "submit", Form: not null } input when eventName is "onclick":
95+
await TriggerFormSubmitAsync(renderer, input, eventArgs, input.Form);
96+
break;
97+
case IHtmlButtonElement { Type: "submit", Form: not null } button when eventName is "onclick":
98+
await TriggerFormSubmitAsync(renderer, button, eventArgs, button.Form);
99+
break;
100+
}
98101
}
99102

100-
private static Task TriggerFormSubmitBubblingEventAsync(ITestRenderer renderer, IElement element, EventArgs eventArgs, IHtmlFormElement form)
103+
private static Task TriggerFormSubmitAsync(ITestRenderer renderer, IElement element, EventArgs eventArgs, IHtmlFormElement form)
101104
{
102-
var events = GetDispatchEventTasks(renderer, element, "onclick", eventArgs);
103-
events = events.Concat(GetDispatchEventTasks(renderer, form, "onsubmit", eventArgs)).ToList();
105+
const string eventName = "onclick";
106+
107+
var eventAttrName = Htmlizer.ToBlazorAttribute(eventName);
108+
var preventDefaultAttrName = $"{eventAttrName}:preventdefault";
109+
if (element.HasAttribute(preventDefaultAttrName))
110+
return Task.CompletedTask;
111+
112+
var events = GetDispatchEventTasks(renderer, form, "onsubmit", eventArgs);
104113

105114
if (events.Count == 0)
106-
throw new MissingEventHandlerException(element, "onclick");
115+
throw new MissingEventHandlerException(element, eventName);
107116

108117
return Task.WhenAll(events);
109118
}
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
<form id="my-form" @onsubmit="() => FormSubmitted = true">
2-
<button id="inside-form-button" type="submit" @onclick="() => Clicked = true">Submit</button>
3-
<input id="inside-form-input" type="submit" @onclick="() => Clicked = true" value="Submit" />
1+
<form id="my-form" @onsubmit="() => FormSubmitted = true">
2+
<button id="inside-form-button" type="submit" @onclick="() => Clicked = true" @onclick:preventDefault=PreventDefault>Submit</button>
3+
<input id="inside-form-input" type="submit" @onclick="() => Clicked = true" @onclick:preventDefault=PreventDefault value="Submit" />
44
</form>
55

66
<button id="outside-form-button" form="my-form">Submit outside</button>
77

88
@code {
99
public bool FormSubmitted;
10-
public bool Clicked;
10+
public bool Clicked;
11+
12+
[Parameter] public bool PreventDefault { get; set; } = false;
1113
}

tests/bunit.web.tests/EventDispatchExtensions/GeneralEventDispatchExtensionsTest.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,18 @@ public void Test306()
266266
Should.Throw<InvalidOperationException>(() => cut.Find("button").Submit());
267267
}
268268

269+
[Fact(DisplayName = "Should not submit a form if the button has preventDefault")]
270+
public void Test307()
271+
{
272+
var cut = RenderComponent<SubmitFormOnClick>(
273+
ComponentParameter.CreateParameter(nameof(SubmitFormOnClick.PreventDefault), true));
274+
275+
cut.Find("#inside-form-input").Click();
276+
277+
cut.Instance.FormSubmitted.ShouldBeFalse();
278+
cut.Instance.Clicked.ShouldBeTrue();
279+
}
280+
269281
public static IEnumerable<object[]> GetTenNumbers() => Enumerable.Range(0, 10)
270282
.Select(i => new object[] { i });
271283

0 commit comments

Comments
 (0)