Add tests for navigate on form submit (#24684)
* Add tests for navigate on form submit * Add fix for re-render after event dispatch * Remove deferred event handling in .NET * Only dispatch events with registered handlers once
This commit is contained in:
parent
40869f8969
commit
577f1a760f
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -2,7 +2,7 @@ import { DotNet } from '@microsoft/dotnet-js-interop';
|
|||
import './GlobalExports';
|
||||
import * as Environment from './Environment';
|
||||
import { monoPlatform } from './Platform/Mono/MonoPlatform';
|
||||
import { renderBatch } from './Rendering/Renderer';
|
||||
import { renderBatch, getRendererer } from './Rendering/Renderer';
|
||||
import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch';
|
||||
import { shouldAutoStart } from './BootCommon';
|
||||
import { setEventDispatcher } from './Rendering/RendererEventDispatcher';
|
||||
|
|
@ -26,7 +26,10 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> {
|
|||
// renderbatch. For example, a renderbatch might mutate the DOM in such a way as to cause an <input> to lose
|
||||
// focus, in turn triggering a 'change' event. It may also be possible to listen to other DOM mutation events
|
||||
// that are themselves triggered by the application of a renderbatch.
|
||||
monoPlatform.invokeWhenHeapUnlocked(() => DotNet.invokeMethodAsync('Microsoft.AspNetCore.Components.WebAssembly', 'DispatchEvent', eventDescriptor, JSON.stringify(eventArgs)));
|
||||
const renderer = getRendererer(eventDescriptor.browserRendererId);
|
||||
if (renderer.eventDelegator.getHandler(eventDescriptor.eventHandlerId)) {
|
||||
monoPlatform.invokeWhenHeapUnlocked(() => DotNet.invokeMethodAsync('Microsoft.AspNetCore.Components.WebAssembly', 'DispatchEvent', eventDescriptor, JSON.stringify(eventArgs)));
|
||||
}
|
||||
});
|
||||
|
||||
// Configure environment for execution under Mono WebAssembly with shared-memory rendering
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const eventPreventDefaultAttributeNamePrefix = 'preventDefault_';
|
|||
const eventStopPropagationAttributeNamePrefix = 'stopPropagation_';
|
||||
|
||||
export class BrowserRenderer {
|
||||
private eventDelegator: EventDelegator;
|
||||
public eventDelegator: EventDelegator;
|
||||
|
||||
private childComponentLocations: { [componentId: number]: LogicalElement } = {};
|
||||
|
||||
|
|
@ -244,7 +244,7 @@ export class BrowserRenderer {
|
|||
this.applyAttribute(batch, componentId, newDomElementRaw, descendantFrame);
|
||||
} else {
|
||||
insertLogicalChild(newDomElementRaw, parent, childIndex);
|
||||
inserted = true;
|
||||
inserted = true;
|
||||
// As soon as we see a non-attribute child, all the subsequent child frames are
|
||||
// not attributes, so bail out and insert the remnants recursively
|
||||
this.insertFrameRange(batch, componentId, newElement, 0, frames, descendantIndex, descendantsEndIndexExcl);
|
||||
|
|
@ -254,7 +254,7 @@ export class BrowserRenderer {
|
|||
|
||||
// this element did not have any children, so it's not inserted yet.
|
||||
if (!inserted) {
|
||||
insertLogicalChild(newDomElementRaw, parent, childIndex);
|
||||
insertLogicalChild(newDomElementRaw, parent, childIndex);
|
||||
}
|
||||
|
||||
// We handle setting 'value' on a <select> in three different ways:
|
||||
|
|
@ -263,10 +263,9 @@ export class BrowserRenderer {
|
|||
// [2] After we finish inserting the <select>, in case the descendant options are being
|
||||
// added as an opaque markup block rather than individually. This is the other case below.
|
||||
// [3] In case the the value of the select and the option value is changed in the same batch.
|
||||
// We just receive an attribute frame and have to set the select value afterwards.
|
||||
// We just receive an attribute frame and have to set the select value afterwards.
|
||||
|
||||
if (newDomElementRaw instanceof HTMLOptionElement)
|
||||
{
|
||||
if (newDomElementRaw instanceof HTMLOptionElement) {
|
||||
// Situation 1
|
||||
this.trySetSelectValueFromOptionElement(newDomElementRaw);
|
||||
} else if (newDomElementRaw instanceof HTMLSelectElement && selectValuePropname in newDomElementRaw) {
|
||||
|
|
@ -406,7 +405,7 @@ export class BrowserRenderer {
|
|||
} else {
|
||||
element.removeAttribute('value');
|
||||
}
|
||||
|
||||
|
||||
// See above for why we have this special handling for <select>/<option>
|
||||
// Situation 3
|
||||
this.trySetSelectValueFromOptionElement(<HTMLOptionElement>element);
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ export class EventDelegator {
|
|||
}
|
||||
}
|
||||
|
||||
public getHandler(eventHandlerId: number) {
|
||||
return this.eventInfoStore.get(eventHandlerId);
|
||||
}
|
||||
|
||||
public removeListener(eventHandlerId: number) {
|
||||
// This method gets called whenever the .NET-side code reports that a certain event handler
|
||||
// has been disposed. However we will already have disposed the info about that handler if
|
||||
|
|
@ -170,6 +174,10 @@ class EventInfoStore {
|
|||
this.addGlobalListener(info.eventName);
|
||||
}
|
||||
|
||||
public get(eventHandlerId: number) {
|
||||
return this.infosByEventHandlerId[eventHandlerId];
|
||||
}
|
||||
|
||||
public addGlobalListener(eventName: string) {
|
||||
if (this.countByEventName.hasOwnProperty(eventName)) {
|
||||
this.countByEventName[eventName]++;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@ export function attachRootComponentToElement(elementSelector: string, componentI
|
|||
attachRootComponentToLogicalElement(browserRendererId || 0, toLogicalElement(element, /* allow existing contents */ true), componentId);
|
||||
}
|
||||
|
||||
export function getRendererer(browserRendererId: number) {
|
||||
return browserRenderers[browserRendererId];
|
||||
}
|
||||
|
||||
export function renderBatch(browserRendererId: number, batch: RenderBatch): void {
|
||||
const browserRenderer = browserRenderers[browserRendererId];
|
||||
if (!browserRenderer) {
|
||||
|
|
|
|||
|
|
@ -559,6 +559,18 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
Browser.Equal("", () => selectWithoutComponent.GetAttribute("value"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NavigateOnSubmitWorks()
|
||||
{
|
||||
var app = Browser.MountTestComponent<NavigateOnSubmit>();
|
||||
var input = app.FindElement(By.Id("text-input"));
|
||||
|
||||
input.SendKeys("Enter");
|
||||
|
||||
var log = Browser.Manage().Logs.GetLog(LogType.Browser);
|
||||
Assert.DoesNotContain(log, entry => entry.Level == LogLevel.Severe);
|
||||
}
|
||||
|
||||
private Func<string[]> CreateValidationMessagesAccessor(IWebElement appElement)
|
||||
{
|
||||
return () => appElement.FindElements(By.ClassName("validation-message"))
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
<option value="BasicTestApp.FormsTest.SimpleValidationComponentUsingExperimentalValidator">Simple validation using experimental validator</option>
|
||||
<option value="BasicTestApp.FormsTest.TypicalValidationComponent">Typical validation</option>
|
||||
<option value="BasicTestApp.FormsTest.TypicalValidationComponentUsingExperimentalValidator">Typical validation using experimental validator</option>
|
||||
<option value="BasicTestApp.NavigateOnSubmit">Navigate to submit</option>
|
||||
<option value="BasicTestApp.GlobalizationBindCases">Globalization Bind Cases</option>
|
||||
<option value="BasicTestApp.HierarchicalImportsTest.Subdir.ComponentUsingImports">Imports statement</option>
|
||||
<option value="BasicTestApp.HtmlBlockChildContent">ChildContent HTML Block</option>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
@using Microsoft.AspNetCore.Components.Forms
|
||||
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<h3>NavigateOnSubmit</h3>
|
||||
|
||||
<EditForm Model="@this" OnValidSubmit="NavigateToPage">
|
||||
<input @onblur="HandleBlur" id="text-input"/>
|
||||
</EditForm>
|
||||
|
||||
@code {
|
||||
public string Name { get; set; }
|
||||
|
||||
public void NavigateToPage()
|
||||
{
|
||||
NavigationManager.NavigateTo("/subdir");
|
||||
}
|
||||
|
||||
public void HandleBlur()
|
||||
{
|
||||
Console.WriteLine("Inside HandleBlur...");
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue