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:
Safia Abdalla 2020-08-12 09:57:58 -07:00 committed by GitHub
parent 40869f8969
commit 577f1a760f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 63 additions and 12 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -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);

View File

@ -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]++;

View File

@ -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) {

View File

@ -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"))

View File

@ -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>

View File

@ -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...");
}
}