import { Component, mount } from "../../src";
import {
onError,
onMounted,
onPatched,
onWillPatch,
onWillStart,
onWillRender,
onRendered,
onWillUnmount,
useState,
xml,
} from "../../src/index";
import {
logStep,
makeTestFixture,
nextTick,
nextMicroTick,
snapshotEverything,
useLogLifecycle,
} from "../helpers";
let fixture: HTMLElement;
snapshotEverything();
let originalconsoleError = console.error;
let mockConsoleError: any;
let originalconsoleWarn = console.warn;
let mockConsoleWarn: any;
beforeEach(() => {
fixture = makeTestFixture();
mockConsoleError = jest.fn(() => {});
mockConsoleWarn = jest.fn(() => {});
console.error = mockConsoleError;
console.warn = mockConsoleWarn;
});
afterEach(() => {
console.error = originalconsoleError;
console.warn = originalconsoleWarn;
});
describe("basics", () => {
test("no component catching error lead to full app destruction", async () => {
class ErrorComponent extends Component {
static template = xml`
hey
`;
}
class Parent extends Component {
static template = xml`
`;
static components = { ErrorComponent };
state = { flag: false };
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("");
parent.state.flag = true;
parent.render();
await nextTick();
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(1);
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test("display a nice error if it cannot find component", async () => {
class SomeComponent extends Component {}
class Parent extends Component {
static template = xml``;
static components = { SomeComponent };
}
let error: Error;
try {
await mount(Parent, fixture);
} catch (e) {
error = e as Error;
}
expect(error!).toBeDefined();
expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
expect(console.error).toBeCalledTimes(0);
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test("display a nice error if it cannot find component (in dev mode)", async () => {
class SomeComponent extends Component {}
class Parent extends Component {
static template = xml``;
static components = { SomeComponent };
}
let error: Error;
try {
await mount(Parent, fixture, { test: true });
} catch (e) {
error = e as Error;
}
expect(error!).toBeDefined();
expect(error!.message).toBe('Cannot find the definition of component "SomeMispelledComponent"');
expect(console.error).toBeCalledTimes(0);
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test("simple catchError", async () => {
class Boom extends Component {
static template = xml``;
}
class Parent extends Component {
static template = xml`
Error
`;
static components = { Boom };
error: any = false;
setup() {
onError((err) => {
this.error = err;
this.render();
});
}
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("Error
");
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
});
describe("errors and promises", () => {
test("a rendering error will reject the mount promise", async () => {
// we do not catch error in willPatch anymore
class App extends Component {
static template = xml`
`;
}
let error: Error;
try {
await mount(App, fixture);
} catch (e) {
error = e as Error;
}
expect(error!).toBeDefined();
const regexp =
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
expect(error!.message).toMatch(regexp);
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleError).toBeCalledTimes(0);
});
test("an error in mounted call will reject the mount promise", async () => {
class App extends Component {
static template = xml`abc
`;
setup() {
onMounted(() => {
throw new Error("boom");
});
}
}
let error: Error;
try {
await mount(App, fixture);
} catch (e) {
error = e as Error;
}
expect(error!).toBeDefined();
expect(error!.message).toBe("boom");
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test("an error in onMounted callback will have the component's setup in its stack trace", async () => {
class App extends Component {
static template = xml`abc
`;
setup() {
onMounted(() => {
throw new Error("boom");
});
}
}
let error: Error;
try {
await mount(App, fixture, { test: true });
} catch (e) {
error = e as Error;
}
expect(error!).toBeDefined();
expect(error!.stack).toContain("App.setup");
expect(error!.stack).toContain("error_handling.test.ts");
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test("errors in onWillRender/onRender aren't wrapped more than once", async () => {
class App extends Component {
static template = xml`abc
`;
setup() {
onWillRender(() => {
throw new Error("boom in onWillRender");
});
onRendered(() => {
throw new Error("boom in onRendered");
});
}
}
let error: Error;
try {
await mount(App, fixture, { test: true });
} catch (e) {
error = e as Error;
}
expect(error!).toBeDefined();
expect(error!.message).toBe(
`The following error occurred in onWillRender: "boom in onWillRender"`
);
});
test("error while rendering component isn't wrapped by onWillRender/onRendered", async () => {
class App extends Component {
static template = xml`abc
`;
setup() {
onWillRender(() => {});
onRendered(() => {});
}
}
let error: Error;
try {
await mount(App, fixture, { test: true });
} catch (e) {
error = e as Error;
}
expect(error!).toBeDefined();
expect(error!.message).toBe("Tokenizer error: could not tokenize `{ 'invalid: 5 }`");
});
test("an error in willPatch call will reject the render promise", async () => {
class Root extends Component {
static template = xml`
`;
val = 3;
setup() {
onWillPatch(() => {
throw new Error("boom");
});
onError((e) => (error = e));
}
}
const root = await mount(Root, fixture, { test: true });
root.val = 4;
let error: Error;
root.render();
await nextTick();
expect(error!).toBeDefined();
expect(error!.message).toBe(`The following error occurred in onWillPatch: "boom"`);
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("an error in patched call will reject the render promise", async () => {
class Root extends Component {
static template = xml`
`;
val = 3;
setup() {
onPatched(() => {
throw new Error("boom");
});
onError((e) => (error = e));
}
}
const root = await mount(Root, fixture, { test: true });
root.val = 4;
let error: Error;
root.render();
await nextTick();
expect(error!).toBeDefined();
expect(error!.message).toBe(`The following error occurred in onPatched: "boom"`);
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("a rendering error in a sub component will reject the mount promise", async () => {
// we do not catch error in willPatch anymore
class Child extends Component {
static template = xml`
`;
}
class App extends Component {
static template = xml`
`;
static components = { Child };
}
let error: Error;
try {
await mount(App, fixture);
} catch (e) {
error = e as Error;
}
expect(error!).toBeDefined();
const regexp =
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
expect(error!.message).toMatch(regexp);
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test("a rendering error will reject the render promise", async () => {
class Root extends Component {
static template = xml`
`;
flag = false;
setup() {
onError((e) => (error = e));
}
}
const root = await mount(Root, fixture);
expect(fixture.innerHTML).toBe("");
root.flag = true;
let error: Error;
root.render();
await nextTick();
expect(error!).toBeDefined();
const regexp =
/Cannot read properties of undefined \(reading 'crash'\)|Cannot read property 'crash' of undefined/g;
expect(error!.message).toMatch(regexp);
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("a rendering error will reject the render promise (with sub components)", async () => {
class Child extends Component {
static template = xml``;
}
class Parent extends Component {
static template = xml`
`;
static components = { Child };
}
let error: Error;
try {
await mount(Parent, fixture);
} catch (e) {
error = e as Error;
}
expect(error!).toBeDefined();
const regexp =
/Cannot read properties of undefined \(reading 'y'\)|Cannot read property 'y' of undefined/g;
expect(error!.message).toMatch(regexp);
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test("errors in mounted and in willUnmount", async () => {
class Example extends Component {
static template = xml``;
val: any;
setup() {
onMounted(() => {
throw new Error("Error in mounted");
this.val = { foo: "bar" };
});
onWillUnmount(() => {
console.log(this.val.foo);
});
}
}
try {
await mount(Example, fixture, { test: true });
} catch (e) {
expect((e as Error).message).toBe(
`The following error occurred in onMounted: "Error in mounted"`
);
}
// 1 additional error is logged because the destruction of the app causes
// the onWillUnmount hook to be called and to fail
expect(mockConsoleError).toBeCalledTimes(1);
expect(mockConsoleWarn).toBeCalledTimes(1);
});
test("errors in rerender", async () => {
class Example extends Component {
static template = xml``;
state: any = { a: { b: 1 } };
}
const root = await mount(Example, fixture);
expect(fixture.innerHTML).toBe("1
");
root.state = "boom";
root.render();
await nextTick();
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(1);
expect(mockConsoleWarn).toBeCalledTimes(1);
});
});
describe("can catch errors", () => {
test("can catch an error in a component render function", async () => {
class ErrorComponent extends Component {
static template = xml`hey
`;
}
class ErrorBoundary extends Component {
static template = xml`
Error handled
`;
state = useState({ error: false });
setup() {
onError(() => (this.state.error = true));
}
}
class App extends Component {
static template = xml`
`;
state = useState({ flag: false });
static components = { ErrorBoundary, ErrorComponent };
}
const app = await mount(App, fixture);
expect(fixture.innerHTML).toBe("");
app.state.flag = true;
await nextTick();
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("calling a hook outside setup should crash", async () => {
class Root extends Component {
static template = xml``;
state = useState({ value: 1 });
setup() {
onWillStart(() => {
this.state = useState({ value: 2 });
});
}
}
let e: Error;
try {
await mount(Root, fixture, { test: true });
} catch (error) {
e = error as Error;
}
expect(e!.message).toBe(
`The following error occurred in onWillStart: "No active component (a hook function should only be called in 'setup')"`
);
});
test("can catch an error in the initial call of a component render function (parent mounted)", async () => {
class ErrorComponent extends Component {
static template = xml`hey
`;
}
class ErrorBoundary extends Component {
static template = xml`
Error handled
`;
state = useState({ error: false });
setup() {
onError(() => {
this.state.error = true;
});
}
}
class App extends Component {
static template = xml`
`;
static components = { ErrorBoundary, ErrorComponent };
}
await mount(App, fixture);
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("can catch an error in the initial call of a component render function (parent updated)", async () => {
class ErrorComponent extends Component {
static template = xml`hey
`;
}
class ErrorBoundary extends Component {
static template = xml`
Error handled
`;
state = useState({ error: false });
setup() {
onError(() => (this.state.error = true));
}
}
class App extends Component {
static template = xml`
`;
state = useState({ flag: false });
static components = { ErrorBoundary, ErrorComponent };
}
const app = await mount(App, fixture);
app.state.flag = true;
await nextTick();
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("can catch an error in the constructor call of a component render function", async () => {
class ErrorComponent extends Component {
static template = xml`Some text
`;
setup() {
throw new Error("NOOOOO");
}
}
class ErrorBoundary extends Component {
static template = xml`
Error handled
`;
state = useState({ error: false });
setup() {
onError(() => (this.state.error = true));
}
}
class App extends Component {
static template = xml`
`;
static components = { ErrorBoundary, ErrorComponent };
}
await mount(App, fixture);
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("can catch an error in the constructor call of a component render function 2", async () => {
class ClassicCompoent extends Component {
static template = xml`classic
`;
}
class ErrorComponent extends Component {
static template = xml`Some text
`;
setup() {
throw new Error("NOOOOO");
}
}
class ErrorBoundary extends Component {
static template = xml`
Error handled
`;
state = useState({ error: false });
setup() {
onError(() => (this.state.error = true));
}
}
class App extends Component {
static template = xml`
`;
static components = { ErrorBoundary, ErrorComponent, ClassicCompoent };
}
await mount(App, fixture);
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("can catch an error in the willStart call", async () => {
class ErrorComponent extends Component {
static template = xml`Some text
`;
setup() {
onWillStart(async () => {
// we wait a little bit to be in a different stack frame
await nextTick();
throw new Error("NOOOOO");
});
}
}
class ErrorBoundary extends Component {
static template = xml`
Error handled
`;
state = useState({ error: false });
setup() {
onError(() => (this.state.error = true));
}
}
class App extends Component {
static template = xml`
`;
static components = { ErrorBoundary, ErrorComponent };
}
await mount(App, fixture);
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("can catch an error origination from a child's willStart function", async () => {
class ClassicCompoent extends Component {
static template = xml`classic
`;
}
class ErrorComponent extends Component {
static template = xml`Some text
`;
setup() {
onWillStart(() => {
throw new Error("NOOOOO");
});
}
}
class ErrorBoundary extends Component {
static template = xml`
Error handled
`;
state = useState({ error: false });
setup() {
onError(() => (this.state.error = true));
}
}
class App extends Component {
static template = xml`
`;
static components = { ErrorBoundary, ErrorComponent, ClassicCompoent };
}
await mount(App, fixture);
expect(fixture.innerHTML).toBe("");
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("can catch an error in the mounted call", async () => {
class ErrorComponent extends Component {
static template = xml`Some text
`;
setup() {
useLogLifecycle();
onMounted(() => {
logStep("boom");
throw new Error("NOOOOO");
});
}
}
class ErrorBoundary extends Component {
static template = xml`
Error handled
`;
state = useState({ error: false });
setup() {
useLogLifecycle();
onError(() => (this.state.error = true));
}
}
class Root extends Component {
static template = xml`
`;
static components = { ErrorBoundary, ErrorComponent };
setup() {
useLogLifecycle();
}
}
await mount(Root, fixture);
expect(fixture.innerHTML).toBe("");
expect([
"Root:setup",
"Root:willStart",
"Root:willRender",
"ErrorBoundary:setup",
"ErrorBoundary:willStart",
"Root:rendered",
"ErrorBoundary:willRender",
"ErrorComponent:setup",
"ErrorComponent:willStart",
"ErrorBoundary:rendered",
"ErrorComponent:willRender",
"ErrorComponent:rendered",
"ErrorComponent:mounted",
"boom",
"ErrorBoundary:willRender",
"ErrorBoundary:rendered",
"ErrorBoundary:mounted",
"Root:mounted",
]).toBeLogged();
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("can catch an error in the mounted call (in root component)", async () => {
class ErrorComponent extends Component {
static template = xml`Some text
`;
setup() {
useLogLifecycle();
onMounted(() => {
logStep("boom");
throw new Error("NOOOOO");
});
}
}
class Root extends Component {
static template = xml`
Error handled
`;
static components = { ErrorComponent };
state = useState({ error: false });
setup() {
useLogLifecycle();
onError(() => (this.state.error = true));
}
}
await mount(Root, fixture);
expect(fixture.innerHTML).toBe("Error handled
");
expect([
"Root:setup",
"Root:willStart",
"Root:willRender",
"ErrorComponent:setup",
"ErrorComponent:willStart",
"Root:rendered",
"ErrorComponent:willRender",
"ErrorComponent:rendered",
"ErrorComponent:mounted",
"boom",
"Root:willRender",
"Root:rendered",
"Root:mounted",
]).toBeLogged();
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("can catch an error in the mounted call (in child of child)", async () => {
class Boom extends Component {
static template = xml`Some text
`;
setup() {
useLogLifecycle();
onMounted(() => {
logStep("boom");
throw new Error("NOOOOO");
});
}
}
class C extends Component {
static template = xml`
Error handled
`;
static components = { Boom };
state = useState({ error: false });
setup() {
useLogLifecycle();
onError(() => (this.state.error = true));
}
}
class B extends Component {
static template = xml`
`;
static components = { C };
setup() {
useLogLifecycle();
}
}
class A extends Component {
static template = xml``;
static components = { B };
setup() {
useLogLifecycle();
}
}
await mount(A, fixture);
expect(fixture.innerHTML).toBe("");
expect([
"A:setup",
"A:willStart",
"A:willRender",
"B:setup",
"B:willStart",
"A:rendered",
"B:willRender",
"C:setup",
"C:willStart",
"B:rendered",
"C:willRender",
"Boom:setup",
"Boom:willStart",
"C:rendered",
"Boom:willRender",
"Boom:rendered",
"Boom:mounted",
"boom",
"C:willRender",
"C:rendered",
"C:mounted",
"B:mounted",
"A:mounted",
]).toBeLogged();
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("error in mounted on a component with a sibling (properly mounted)", async () => {
class ErrorComponent extends Component {
static template = xml`Some text
`;
setup() {
useLogLifecycle();
onMounted(() => {
logStep("boom");
throw new Error("NOOOOO");
});
}
}
class ErrorBoundary extends Component {
static template = xml`
Error handled
`;
state = useState({ error: false });
setup() {
useLogLifecycle();
onError(() => (this.state.error = true));
}
}
class OK extends Component {
static template = xml`OK`;
setup() {
useLogLifecycle();
}
}
class Root extends Component {
static template = xml`
`;
static components = { ErrorBoundary, ErrorComponent, OK };
setup() {
useLogLifecycle();
}
}
await mount(Root, fixture);
expect(fixture.innerHTML).toBe("");
expect([
"Root:setup",
"Root:willStart",
"Root:willRender",
"OK:setup",
"OK:willStart",
"ErrorBoundary:setup",
"ErrorBoundary:willStart",
"Root:rendered",
"OK:willRender",
"OK:rendered",
"ErrorBoundary:willRender",
"ErrorComponent:setup",
"ErrorComponent:willStart",
"ErrorBoundary:rendered",
"ErrorComponent:willRender",
"ErrorComponent:rendered",
"ErrorComponent:mounted",
"boom",
"ErrorBoundary:willRender",
"ErrorBoundary:rendered",
"ErrorBoundary:mounted",
"OK:mounted",
"Root:mounted",
]).toBeLogged();
expect(mockConsoleError).toBeCalledTimes(0);
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("can catch an error in the willPatch call", async () => {
class ErrorComponent extends Component {
static template = xml`
`;
setup() {
onWillPatch(() => {
throw new Error("NOOOOO");
});
}
}
class ErrorBoundary extends Component {
static template = xml`
Error handled
`;
state = useState({ error: false });
setup() {
onError(() => (this.state.error = true));
}
}
class App extends Component {
static template = xml`
`;
state = useState({ message: "abc" });
static components = { ErrorBoundary, ErrorComponent };
}
const app = await mount(App, fixture);
expect(fixture.innerHTML).toBe("");
app.state.message = "def";
await nextTick();
await nextTick();
await nextTick();
expect(fixture.innerHTML).toBe("");
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("catchError in catchError", async () => {
class Boom extends Component {
static template = xml``;
}
class Child extends Component {
static template = xml`
`;
static components = { Boom };
setup() {
onError((error) => {
throw error;
});
}
}
class Parent extends Component {
static template = xml`
Error
`;
static components = { Child };
error: any = false;
setup() {
onError((error) => {
this.error = error;
this.render();
});
}
}
await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("Error
");
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("onError in class inheritance is not called if no rethrown", async () => {
const steps: string[] = [];
class Abstract extends Component {
static template = xml`
`;
state: any;
setup() {
this.state = useState({});
onError(() => {
steps.push("Abstract onError");
this.state.error = "Abstract";
});
}
}
class Concrete extends Abstract {
setup() {
super.setup();
onError(() => {
steps.push("Concrete onError");
this.state.error = "Concrete";
});
}
}
class Parent extends Component {
static components = { Concrete };
static template = xml``;
}
await mount(Parent, fixture);
expect(steps).toStrictEqual(["Concrete onError"]);
expect(fixture.innerHTML).toBe("Concrete
");
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("onError in class inheritance is called if rethrown", async () => {
const steps: string[] = [];
class Abstract extends Component {
static template = xml`
`;
state: any;
setup() {
this.state = useState({});
onError(() => {
steps.push("Abstract onError");
this.state.error = "Abstract";
});
}
}
class Concrete extends Abstract {
setup() {
super.setup();
onError((error) => {
steps.push("Concrete onError");
this.state.error = "Concrete";
throw error;
});
}
}
class Parent extends Component {
static components = { Concrete };
static template = xml``;
}
await mount(Parent, fixture);
expect(steps).toStrictEqual(["Concrete onError", "Abstract onError"]);
expect(fixture.innerHTML).toBe("Abstract
");
expect(mockConsoleWarn).toBeCalledTimes(0);
});
test("catching error, rethrow, render parent -- a main component loop implementation", async () => {
let parentState: any;
class ErrorComponent extends Component {
static template = xml``;
setup() {
throw new Error("My Error");
}
}
class Child extends Component {
static template = xml``;
static components = { ErrorComponent };
setup() {
onError((error) => {
throw error;
});
}
}
class Sibling extends Component {
static template = xml`Sibling
`;
}
class ErrorHandler extends Component {
static template = xml``;
setup() {
onError(() => {
this.props.onError();
Promise.resolve().then(() => {
parentState.cps[2] = {
id: 2,
Comp: Sibling,
};
});
});
}
}
class Parent extends Component {
static template = xml`
`;
static components = { ErrorHandler };
state: any = useState({
cps: {},
});
setup() {
parentState = this.state;
}
cleanUp(id: number) {
delete this.state.cps[id];
}
}
await mount(Parent, fixture);
parentState.cps[1] = { id: 1, Comp: Child };
await nextMicroTick();
expect(fixture.innerHTML).toBe("");
await nextTick();
expect(fixture.innerHTML).toBe("Sibling
");
});
test("catching in child makes parent render", async () => {
class Child extends Component {
static template = xml``;
}
class ErrorComp extends Component {
static template = xml``;
setup() {
throw new Error("Error Component");
}
}
class Catch extends Component {
static template = xml``;
setup() {
onError((error) => {
this.props.onError(error);
});
}
}
const steps: any[] = [];
class Parent extends Component {
static components = { Catch };
static template = xml`
`;
elements: any = {};
onError(id: any, error: Error) {
steps.push(error.message);
delete this.elements[id];
this.elements[2] = Child;
this.render();
}
}
const parent = await mount(Parent, fixture);
expect(fixture.innerHTML).toBe("");
parent.elements[1] = ErrorComp;
parent.render();
await nextTick();
expect(fixture.innerHTML).toBe("Child 2
");
expect(steps).toEqual(["Error Component"]);
});
});