parent
f3b0fbe207
commit
a68c961a58
|
|
@ -96,6 +96,25 @@ app {
|
|||
color: red;
|
||||
}
|
||||
|
||||
#error-ui {
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.main .top-row {
|
||||
display: none;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
|
|
@ -8,9 +9,16 @@
|
|||
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
|
||||
<link href="css/site.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<app>Loading...</app>
|
||||
|
||||
<div id="error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
<script src="_framework/blazor.webassembly.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -12,4 +12,10 @@
|
|||
<div class="content px-4">
|
||||
@Body
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a class="reload">Reload</a>
|
||||
<a class="dismiss">X</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -111,6 +111,23 @@ app {
|
|||
color: red;
|
||||
}
|
||||
|
||||
#error-ui {
|
||||
background: lightyellow;
|
||||
position: fixed;
|
||||
border: "1px solid";
|
||||
border-color: black;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#error-ui .dismiss {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.main .top-row {
|
||||
display: none;
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -2,6 +2,7 @@ import '@dotnet/jsinterop';
|
|||
import './GlobalExports';
|
||||
import * as signalR from '@aspnet/signalr';
|
||||
import { MessagePackHubProtocol } from '@aspnet/signalr-protocol-msgpack';
|
||||
import { showErrorNotification } from './BootErrors';
|
||||
import { shouldAutoStart } from './BootCommon';
|
||||
import { RenderQueue } from './Platform/Circuits/RenderQueue';
|
||||
import { ConsoleLogger } from './Platform/Logging/Loggers';
|
||||
|
|
@ -106,6 +107,7 @@ async function initializeConnection(options: BlazorOptions, logger: Logger, circ
|
|||
connection.on('JS.Error', error => {
|
||||
renderingFailed = true;
|
||||
unhandledError(connection, error, logger);
|
||||
showErrorNotification();
|
||||
});
|
||||
|
||||
window['Blazor']._internal.forceCloseConnection = () => connection.stop();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
let hasFailed = false;
|
||||
|
||||
export async function showErrorNotification() {
|
||||
let errorUi = document.querySelector('#error-ui') as HTMLElement;
|
||||
if (errorUi) {
|
||||
errorUi.style.display = 'block';
|
||||
}
|
||||
|
||||
if (!hasFailed) {
|
||||
hasFailed = true;
|
||||
const errorUiReloads = document.querySelectorAll<HTMLElement>('#error-ui .reload');
|
||||
errorUiReloads.forEach(reload => {
|
||||
reload.onclick = function (e) {
|
||||
location.reload();
|
||||
e.preventDefault();
|
||||
};
|
||||
});
|
||||
|
||||
let errorUiDismiss = document.querySelectorAll<HTMLElement>('#error-ui .dismiss');
|
||||
errorUiDismiss.forEach(dismiss => {
|
||||
dismiss.onclick = function (e) {
|
||||
const errorUi = document.querySelector<HTMLElement>('#error-ui');
|
||||
if (errorUi) {
|
||||
errorUi.style.display = 'none';
|
||||
}
|
||||
e.preventDefault();
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { MethodHandle, System_Object, System_String, System_Array, Pointer, Platform } from '../Platform';
|
||||
import { getFileNameFromUrl } from '../Url';
|
||||
import { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger';
|
||||
import { showErrorNotification } from '../../BootErrors';
|
||||
|
||||
const assemblyHandleCache: { [assemblyName: string]: number } = {};
|
||||
const typeHandleCache: { [fullyQualifiedTypeName: string]: number } = {};
|
||||
|
|
@ -232,7 +233,11 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: ()
|
|||
const suppressMessages = ['DEBUGGING ENABLED'];
|
||||
|
||||
module.print = line => (suppressMessages.indexOf(line) < 0 && console.log(`WASM: ${line}`));
|
||||
module.printErr = line => console.error(`WASM: ${line}`);
|
||||
|
||||
module.printErr = line => {
|
||||
console.error(`WASM: ${line}`);
|
||||
showErrorNotification();
|
||||
};
|
||||
module.preRun = [];
|
||||
module.postRun = [];
|
||||
module.preloadPlugins = [];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using BasicTestApp;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using OpenQA.Selenium;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||
{
|
||||
[Collection("ErrorNotification")] // When the clientside and serverside tests run together it seems to cause failures, possibly due to connection lose on exception.
|
||||
public class ErrorNotificationClientSideTest : ServerTestBase<ToggleExecutionModeServerFixture<Program>>
|
||||
{
|
||||
public ErrorNotificationClientSideTest(
|
||||
BrowserFixture browserFixture,
|
||||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
// On WebAssembly, page reloads are expensive so skip if possible
|
||||
Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client);
|
||||
Browser.MountTestComponent<ErrorComponent>();
|
||||
Browser.Exists(By.Id("error-ui"));
|
||||
Browser.Exists(By.TagName("button"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShowsErrorNotification_OnError_Dismiss()
|
||||
{
|
||||
var errorUi = Browser.FindElement(By.Id("error-ui"));
|
||||
Assert.Equal("none", errorUi.GetCssValue("display"));
|
||||
|
||||
var causeErrorButton = Browser.FindElement(By.TagName("button"));
|
||||
causeErrorButton.Click();
|
||||
|
||||
Browser.Exists(By.CssSelector("#error-ui[style='display: block;']"), TimeSpan.FromSeconds(10));
|
||||
|
||||
var reload = Browser.FindElement(By.ClassName("reload"));
|
||||
reload.Click();
|
||||
|
||||
Browser.DoesNotExist(By.TagName("button"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShowsErrorNotification_OnError_Reload()
|
||||
{
|
||||
var causeErrorButton = Browser.Exists(By.TagName("button"));
|
||||
var errorUi = Browser.FindElement(By.Id("error-ui"));
|
||||
Assert.Equal("none", errorUi.GetCssValue("display"));
|
||||
|
||||
causeErrorButton.Click();
|
||||
Browser.Exists(By.CssSelector("#error-ui[style='display: block;']"));
|
||||
|
||||
var dismiss = Browser.FindElement(By.ClassName("dismiss"));
|
||||
dismiss.Click();
|
||||
Browser.Exists(By.CssSelector("#error-ui[style='display: none;']"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using BasicTestApp;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using OpenQA.Selenium;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||
{
|
||||
[Collection("ErrorNotification")] // When the clientside and serverside tests run together it seems to cause failures, possibly due to connection lose on exception.
|
||||
public class ErrorNotificationServerSideTest : ErrorNotificationClientSideTest
|
||||
{
|
||||
public ErrorNotificationServerSideTest(
|
||||
BrowserFixture browserFixture,
|
||||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture.WithServerExecution(), output)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<div>
|
||||
<h2>Error throwing button</h2>
|
||||
<p>
|
||||
<button @onclick="@(IncrementCount)">Click me</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
int currentCount = 0;
|
||||
|
||||
void IncrementCount()
|
||||
{
|
||||
currentCount++;
|
||||
throw new NotImplementedException("Doing crazy things!");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
@using Microsoft.AspNetCore.Components.Rendering
|
||||
<div id="test-selector">
|
||||
Select test:
|
||||
Select test:
|
||||
<select id="test-selector-select" @bind=SelectedComponentTypeName>
|
||||
<option value="none">Choose...</option>
|
||||
<option value="BasicTestApp.AddRemoveChildComponents">Add/remove child components</option>
|
||||
|
|
@ -20,6 +20,7 @@
|
|||
<option value="BasicTestApp.DispatchingComponent">Dispatching to sync context</option>
|
||||
<option value="BasicTestApp.DuplicateAttributesComponent">Duplicate attributes</option>
|
||||
<option value="BasicTestApp.ElementRefComponent">Element ref component</option>
|
||||
<option value="BasicTestApp.ErrorComponent">Error throwing</option>
|
||||
<option value="BasicTestApp.EventBubblingComponent">Event bubbling</option>
|
||||
<option value="BasicTestApp.EventCallbackTest.EventCallbackCases">EventCallback</option>
|
||||
<option value="BasicTestApp.EventCasesComponent">Event cases</option>
|
||||
|
|
@ -82,6 +83,12 @@
|
|||
@((RenderFragment)RenderSelectedComponent)
|
||||
</app>
|
||||
|
||||
<div id="error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href class='reload'>Reload</a>
|
||||
<a class='dismiss' style="cursor: pointer;">🗙</a>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
string SelectedComponentTypeName { get; set; } = "none";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Basic test app</title>
|
||||
|
|
@ -9,6 +10,7 @@
|
|||
<!-- Used by ExternalContentPackage -->
|
||||
<link href="_content/TestContentPackage/styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<root>Loading...</root>
|
||||
|
||||
|
|
@ -31,4 +33,5 @@
|
|||
<!-- Used by ExternalContentPackage -->
|
||||
<script src="_content/TestContentPackage/prompt.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,14 @@
|
|||
outline: 1px solid red;
|
||||
}
|
||||
|
||||
#error-ui {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#error-ui dismiss {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
color: red;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,17 @@
|
|||
@(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))
|
||||
</app>
|
||||
|
||||
<div id="error-ui">
|
||||
<environment include="Staging,Production">
|
||||
An error has occurred. This application may no longer respond until reloaded.
|
||||
</environment>
|
||||
<environment include="Development">
|
||||
An unhandled exception has occurred. See browser dev tools for details.
|
||||
</environment>
|
||||
<a href class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
|
||||
<script src="_framework/blazor.server.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -111,6 +111,25 @@ app {
|
|||
color: red;
|
||||
}
|
||||
|
||||
#error-ui {
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.main .top-row {
|
||||
display: none;
|
||||
|
|
|
|||
|
|
@ -47,6 +47,13 @@ namespace Microsoft.AspNetCore.E2ETesting
|
|||
public static IWebElement Exists(this IWebDriver driver, By finder)
|
||||
=> Exists(driver, finder, default);
|
||||
|
||||
public static void DoesNotExist(this IWebDriver driver, By finder, TimeSpan timeout = default)
|
||||
=> WaitAssertCore(driver, () =>
|
||||
{
|
||||
var elements = driver.FindElements(finder);
|
||||
Assert.Empty(elements);
|
||||
}, timeout);
|
||||
|
||||
public static IWebElement Exists(this IWebDriver driver, By finder, TimeSpan timeout)
|
||||
=> WaitAssertCore(driver, () =>
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue